第十二章:IntentService

在第11章中,我们讨论了服务生命周期如何处理异步执行,同时增加进程级别并避免运行时终止后台线程。然而,服务本身并不是一种异步技术,因为它在UI线程上执行。这个缺点在扩展Service类的In tentService中得到了解决。IntentService具有服务生命周期的属性,但也在后台线程上添加了内置任务处理。

Fundamentals 基础知识

IntentService在单个后台线程上执行任务-即,所有任务都被顺序地执行。IntentService的用户通过使用Context. startService传递Intent来触发异步执行。如果IntentService正在运行,Intent将排队等待,直到后台线程准备好处理它。如果Intent Service未运行,则会启动新的组件生命周期,并在没有更多Intent要处理时结束。因此,IntentService仅在有任务要执行时运行。

与任务控制的服务(第190页的“任务控制的服务”)一样,IntentSer服务始终具有活动组件,从而降低了提前终止任务的风险。

注意:IntentService中的后台任务执行器是一个线程。与AsyncTask中的默认执行器不同,IntentSer的副执行器是每个实例而不是每个应用程序。因此,一个应用程序可以有多个IntentService实例,其中每个实例顺序执行任务,但独立于其他Intent Service实例。

要使用IntentService,请使用应用程序特定的实现覆盖它,在AndroidManifest.xml中将其声明为Service组件:

<service android:name=".SimpleIntentService"/>

IntentService子类只需实现onHandleIntent方法,因为下面的SimpleIntentService显示:

public class SimpleIntentService extends IntentService {
    public SimpleIntentService() {
        super(SimpleIntentService.class.getName());
        setIntentRedelivery(true);
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        // Called on a background thread
    }
}

构造函数必须用一个命名后台线程的字符串来调用超类-用于调试目的。这里也是指定如果进程被杀死是否应该恢复IntentService的地方。默认情况下,只有在存在挂起的启动请求时才会恢复IntentService,但调用setIntentRedirection(true)将重新交付最后交付的Intent。

注意:IntentService内部处理第184页“重启操作”中描述的两种启动类型START_NOT_STICKY和START_REDELIVER_INTENT。第一个是默认值,因此后者需要使用setIntentRedirection(true)进行设置。

想要使用IntentService的客户端使用Context.start Service创建一个启动请求,并传递一个Intent,其中包含服务应该处理的数据:

public class SimpleActivity extends Activity {
    public void onButtonClick(View v) {
        Intent intent = new Intent(this, SimpleIntentService.class);
        intent.putExtra("data", data);
        startService(intent);
    }
}

注意:不需要使用stopSelf停止IntentService,因为这是在内部完成的。

Good Ways to Use an IntentService 使用IntentService的好方法

IntentService适合于您想要轻松地将任务从UI线程卸载到具有顺序任务处理的后台线程,为任务提供一个始终处于活动状态的组件,以提高进程排名。

Sequentially Ordered Tasks -按顺序排列的任务

应该独立于原始组件顺序执行的任务可以使用IntentService来确保所有提交的任务都在活动IntentService组件中排队。

Example: Web service communication

与网络资源(例如Web服务)的通信通常以顺序方式进行,即,一个资源被检索,它包含了将来如何与其他资源交互的指令。HTTP协议使用GET、POST、PUT和POST请求类型与网络资源交互。请求可以起源于用户交互或计划的系统操作,但可以由IntentService处理。

在本例中,请求源自Activity,通常由用户操作初始化。为了简单起见,只处理最常见的请求类型:获取数据的GET和发送数据的POST。两者都被卸载到IntentService。来自请求的响应在ResultReceiver中返回:

public class WebServiceActivity extends Activity {
    private final static String getUrl = "...";
    private final static String postUrl = "...";
    private ResultReceiver mReceiver;
    public WebServiceActivity() {
        mReceiver = new ResultReceiver(new Handler()) {     //1
            @Override
            protected void onReceiveResult(int resultCode, Bundle resultData) {
                int httpStatus = resultCode;
                String jsonResult = null;
                if (httpStatus == 200) { // OK
                    if (resultData != null) {
                        jsonResult= resultData.getString(
                                WebService.BUNDLE_KEY_REQUEST_RESULT);
                        // Omitted: Handle response
                    }
                }
                else {
                        // Omitted: Handle error
                }
            }
        };
    }
    private void doPost() {     //2
        Intent intent = new Intent(this, WebService.class);
        intent.setData(Uri.parse(postUrl));
        intent.putExtra(WebService.INTENT_KEY_JSON, "{\"foo\":\"bar\"}");
        intent.putExtra(WebService.INTENT_KEY_RECEIVER, mReceiver);
        startService(intent);
    }
    private void doGet() {      //3
        Intent intent = new Intent(this, WebService.class);
        intent.setData(Uri.parse(getUrl));
        intent.putExtra(WebService.INTENT_KEY_RECEIVER, mReceiver);
        startService(intent);
    }
}

1,创建传递给IntentService的ResultReceiver,以便可以返回操作的结果。

2,发出包含JSON格式内容的POST请求。

3,发出GET请求。

IntentService在onHandleIntent中接收请求并按顺序处理它们。来自WebServiceActivity的数据决定了请求类型、URL、ResultReceiver以及可能要发送的数据:

public class WebService extends IntentService {
    private static final String TAG = WebService.class.getName();
    public static final int GET = 1;
    public static final int POST = 2;
    public static final String INTENT_KEY_REQUEST_TYPE =
            "com.eat.INTENT_KEY_REQUEST_TYPE";
    public static final String INTENT_KEY_JSON =
            "com.eat.INTENT_KEY_JSON";
    public static final String INTENT_KEY_RECEIVER =
            "com.eat.INTENT_KEY_RECEIVER";
    public static final String BUNDLE_KEY_REQUEST_RESULT =
            "com.eat.BUNDLE_KEY_REQUEST_RESULT";
    public WebService() {
        super(TAG);
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        Uri uri = intent.getData();     //1
        int requestType = intent.getIntExtra(INTENT_KEY_REQUEST_TYPE, 0);
        String json = (String)intent.getSerializableExtra(INTENT_KEY_JSON);
        ResultReceiver receiver = intent.getParcelableExtra(INTENT_KEY_RECEIVER);
        try {
            HttpRequestBase request = null;
            switch (requestType) {      //2
                case GET: {
                    request = new HttpGet();
                    // Request setup omitted
                    break;
                }
                case POST: {
                    request = new HttpPost();
                    if (json != null) {
                        ((HttpPost)request).setEntity(new StringEntity(json));
                    }
                    // Request setup omitted
                    break;
                }
            }
            if (request != null) {
                request.setURI(new URI(uri.toString()));
                HttpResponse response = doRequest(request);     //3
                HttpEntity httpEntity = response.getEntity();
                StatusLine responseStatus = response.getStatusLine();
                int statusCode = responseStatus != null ?
                        responseStatus.getStatusCode() : 0;
                if (httpEntity != null) {
                    Bundle resultBundle = new Bundle();
                    resultBundle.putString(BUNDLE_KEY_REQUEST_RESULT,
                            EntityUtils.toString(httpEntity));
                    receiver.send(statusCode, resultBundle);    //4
                }
                else {
                    receiver.send(statusCode, null);
                }
            }
            else {
                receiver.send(0, null);
            }
        } catch (IOException e) {
            receiver.send(0, null);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }
    private HttpResponse doRequest(HttpRequestBase request) throws IOException {
        HttpClient client = new DefaultHttpClient();
    // HttpClient configuration omitted
        return client.execute(request);
    }
}

1,从Intent中删除必要的数据。

2,根据Intent数据创建请求类型。

3,发出网络请求。

4,将成功的结果返回给WebServiceActivity。

Asynchronous Execution in BroadcastReceiver -BroadcastReceiver中的异步执行

BroadcastReceiver是一个应用程序入口点-即,它可以是进程中启动的第一个Android组件。启动可以从其他应用程序或系统服务触发。无论哪种方式,BroadcastReceiver都会在onReceive回调中接收Intent,该回调在UI线程上调用。因此,如果需要执行任何长时间运行的操作,则需要异步执行。

但是,BroadcastReceiver组件仅在onReceive执行期间活动。因此,在组件被销毁后,异步任务可能会继续执行-如果BroadcastReceiver是入口点,则进程为空-这可能会使运行时在任务完成之前杀死进程。然后任务的结果丢失。

为了避免空进程的问题,IntentService是BroadcastReceiver异步执行的理想候选对象。一旦从BroadcastReceiver发送了一个启动请求,onReceive完成就不是问题了,因为在后台执行期间有一个新组件处于活动状态。

使用goAsync延长使用寿命

从API级别11开始,BroadcastReceiver.goAsync()方法可用于简化异步执行。它将异步结果的状态保存在Broadcas tReceiver.PendingResult中,并将广播的生存期延长到BroacastReceiver.PendingResult用finish显式终止,可以在异步执行完成后调用finish。

AsyncReceiver中显示了一个最小的异步接收器,其中BroadcastReceiver保持活动状态,直到PendingResult完成:

public class AsyncReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {
        final PendingResult result = goAsync();
        new thread() {
            public void run() {
                // Do background work
                result.finish();
            }
        }.start();
    }
}

Example: Periodical long operations -周期性长操作

即使应用程序本身没有执行也应该触发周期性任务的应用程序可以利用平台中的AlarmManager系统服务。这可以配置一个周期性的时间间隔,当它将发送一个Intent到应用程序中的BroadcastRe接收器。因此,如果应用程序没有运行,BroadcastRe接收器是应用程序的入口点,长时间运行的操作应该在另一个组件中执行,通常是IntentService。

此示例检查网络资源,以查看自上次运行IntentService以来是否进行了任何更新。如果是,则将通知添加到状态栏。

BroadcastReceiver和AlarmManager在Activity中设置:

public class AlarmBroadcastActivity extends Activity {
    private static final long ONE_HOUR = 60 * 60 * 1000;
    AlarmManager am;
    AlarmReceiver alarmReceiver;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        alarmReceiver = new AlarmReceiver();
        registerReceiver(alarmReceiver, new IntentFilter(
                "com.eat.alarmreceiver"));  //1
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0,
                new Intent("com.eat.alarmreceiver"),
                PendingIntent.FLAG_UPDATE_CURRENT);
        am = (AlarmManager)(this.getSystemService( Context.ALARM_SERVICE ));
        am.setRepeating(AlarmManager.ELAPSED_REALTIME,
                SystemClock.elapsedRealtime() + ONE_HOUR, ONE_HOUR, pendingIntent); //2
    }
}

1,注册将从AlarmManager接收Intent的BroadcastReceiver。

2,配置AlarmManager,使其每小时启动应用程序。

AlarmReceiver每小时启动一次,并将调用重定向到IntentService可以处理网络操作:

public class AlarmReceiver extends BroadcastReceiver {
    public void onReceive(Context context, Intent intent) {
        context.startService(new Intent(context,
                NetworkCheckerIntentService.class));
    }
}

NetworkHandkerIntentService在onHandleIntent中接收启动请求,进行网络呼叫,并可能更新状态栏:

public class NetworkCheckerIntentService extends IntentService {
    public NetworkCheckerIntentService() {
        super("NetworkCheckerThread");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        if (isNewNetworkDataAvailable()) {      //1
            addStatusBarNotification();
        }
    }
    private boolean isNewNetworkDataAvailable() {
        // Network request code omitted. Return dummy result.
        return true;
    }
    private void addStatusBarNotification() {
        Notification.Builder mBuilder =
                new Notification.Builder(this)
                        .setSmallIcon(R.drawable.new_data_available)
                        .setContentTitle("New network data")
                        .setContentText("New data can be downloaded.");
        NotificationManager mNotificationManager =
                (NotificationManager) getSystemService(
                        Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(1, mBuilder.build());
    }
}

1,包含网络呼叫。

IntentService与Service 对比

IntentService从Service继承了其特征:相同的声明、对进程等级的相同影响以及客户端的相同启动请求过程。它实现了Service的启动请求处理语义,因此使用Intent Service的应用程序只需实现onHandleIntent即可。因此,IntentService的使用与常用的任务控制服务(第190页的“任务控制服务”)相匹配,但内置了对异步执行和组件生命周期管理的支持。

IntentService使用起来非常简单,并且通常是正确用例的完美解决方案,例如刚才描述的用例。然而,简单性也有局限性,服务可能是首选:

Control by clients 客户端控制

当您希望组件的生命周期由其他组件控制时,请选择用户控制的服务(第186页的“用户控制的服务”)。这适用于已启动和已绑定的服务。

Concurrent task execution 并发任务执行

并发执行任务,在Service中启动多个线程。

Sequential and rearrangeable tasks 顺序和可重复的任务

可以对任务进行优先级排序,以便绕过任务队列。例如,通过按钮控制的音乐服务-播放,暂停,倒带,快进,停止等-通常会优先处理停止请求,以便在队列中的任何其他任务之前执行该请求。这需要一个服务。

总结:

IntentService是一个易于使用的顺序任务处理器,它不仅对于从UI线程卸载操作非常有用,而且对于从其他原始组件卸载操作也非常有用。本书中讨论的其他顺序任务处理器,如Thread、Executors.newSingleThreadExecutor和AsyncTask在某种程度上可以与IntentService相比较,但IntentService具有作为独立组件运行的优势,而其他组件则没有。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值