在之前的一篇文章《基于场景解读Android四大组件》中谈到Service是Android提供给开发者的一个组件,主要用于后台一些耗时任务的处理。其实Android系统中已经存在了很多这样在后台执行一些特定任务的系统级Service,比方说与我们开发中打交道最多的ActivityManager,WindowManager,PackageManager和InputManager等等。今天我们依然从具体使用场景来对Android中Service的具体功能进行分析。

Service生命周期

android怎么使用service代替activity_后台任务

从图中可以看出Service的生命周期会根据启动方式的不同有不同的生命周期回调。其实startService和bindService的区别就是该service是否可以和启动它的组件(比如activity)通信,因为bindService可以拿到Service的binder,binder就是用来实现IPC的嘛。下面我们具体分析下每个生命周期回调:

onCreate

该接口是在Service实例被创建时调用,这里的Service实例跟Activity实例不一样,我们知道Activity实例根据不同的启动模式可以有一个或者多个实例,但是,Service虽然也有两种启动方式,在整个系统中却只会有一个Service实例。为什么呢?换个角度看,这就好比PC端的C/S模式,使用一个服务端去处理多个客户端的请求,这里就对应一个Service去处理来自多个Activity的请求嘛,没必要搞多个,浪费资源,而且你会发现系统级Service其实也都只有一个实例。那么在onCreate里面我们可以做些什么呢?当然是初始化,比如创建数据缓存,线程池等等。Android系统给我们提供了一个IntentService,我们可以参考它的实现方式来做一些初始化操作,IntentService的onCreate源码如下:

@Override
public void onCreate() {
    // TODO: It would be nice to have an option to hold a partial wakelock
    // during processing, and to have a static startService(Context, Intent)
    // method that would launch the service & hand off a wakelock.

    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

这里为什么要创建新的线程或者线程池呢?因为Service默认是在主线程中执行的,所以我不建议你把一个后台任务放在Service中的主线程执行,因为那样就失去了Service存在的初衷,还不如直接放在Activity里面做,除非你想要提升App进程的优先级,防止App退到后台被杀掉。

onStart

该接口是在调用startService方法时调用的,我们的后台任务一般都会放在这里执行,你可以通过intent获取startService方法传递的参数,这里依然以IntentService为例看下它的实现方式:

@Override
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

onBind

该方法是在调用bindService方法时调用,如果使用bindService方式来启动服务的话,一般发生在Activity需要与Service进行通信的场景(比如说音乐播放器app里面就会用到),而Android的IPC主要是通过binder来实现的(也可以通过socket,在系统服务用的比较多),所以这里方法的返回值就需要一个binder实例。这里简单说下binder的实现机制(后续讲Broadcast的时候我们在具体分析Android的IPC机制具体实现),其实就是一套PC上的C/S模式,用户通过bindService接口获取到Service的代理,然后通过这个代理来跟Service通信。我们这里用一段代码来详细说明下:

public class MyService extends Service {
    Binder mService = new IMyAidlInterface.Stub() {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
    };

    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mService;
    }
}
// call in activity
bindService(new Intent(this, MyService.class), new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mServiceProxy = IMyAidlInterface.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
}, 0);

这里的代理分两种:本地代理和远程代理,它们是按照service和activity是否在同一个进程中来区分的(这里仅以activity和service通信为例来说明,其他场景原理类似),如果是在同一进程,那么这个代理对象mServiceProxy其实就是mService对象。而当它们不在同一个进程中时,mServiceProxy和mService就属于不同进程空间中的对象,由于不同进程之间的数据不能直接访问,所以这个时候binder driver就来充当一个中间桥梁的作用,来完成参数和返回结果等数据的传递(其实也就是在Linux内核空间开辟了一段共享内存),从而实现通信的目的。当然为了方便开发者使用binder,Android对binder的使用进行了一定的封装,提供了一个AIDL。通过AIDL我们就只需关心service提供的功能接口,而不用去关心这些接口调用的具体细节。所以从这里也可以看出,对于一个好的产品,不管它的用户群是普通用户还是程序员,使用的便捷性都是一个很重要的指标。就好比现在市面上很多做SDK的,往往那些接口简单,文档清晰的SDK,用的人也会多一些。

onRebind

该方法是在多次调用bindService和unbindService时会调用到该接口。该方法使用场景不多,一般我们不会在这里面做一些事情,不过可能会有一些数据统计放在这里以观察用户的某一操作行为。

onUnbind

该方法是在调用unbindService方法时调用,一般发生在activity中需要断开与service的连接的场景。注意该接口有个返回值,默认为false。如果你想要在onRebind里面做一些事情的话,那么这里需要返回true。

onDestroy

该方法会在Service销毁时调用,一般在这里我们会释放一些在onCreate中进行初始化时所申请的资源,可以参考IntentService的实现方式:

@Override
public void onDestroy() {
    mServiceLooper.quit();
}

一般可以通过stopService或者unbindService方式来销毁不再需要的Service。而unbindService这种方式必须是没有通过startService启动Service的情况,否则不会销毁Service。

Service使用场景

为了满足开发者处理后台任务的需要,Android提供了Service这个组件,同时为了方便开发者使用Service,又封装了一个IntentService。当然,现在很多App在处理后台任务的时候并没有优先使用Service,而是自己实现了一套线程池机制或者使用Android提供的AsyncTask来执行后台任务,这里我们来分析下他们各自的优劣:

  • Service的优点是系统原生支持,使用方便;创建进程方便;可以提供给系统内其他App使用;优先级高,当App退到后台后不宜被杀死。缺点是由于启动Service涉及到多次IPC,运行效率不高,而且受限于系统接口,使用不够灵活。
  • 线程池的优点是运行效率高,配置和使用灵活。缺点是多进程实现不方便, 由于Android实现了一套进程托管机制,我们不能直接创建一个新的进程,而只能通过四大组件的形式创建新的进程。

基于以上分析,我们可以看出,一般普通的异步任务,比如网络请求,数据库或者文件相关操作,我们都会使用线程池的方式来做,因为这样使用的系统开销小,运行效率高,而且随着业务逻辑的复杂度增加,扩展性也更强。然而,对于一些特殊场景,比如进程保活,使用第三方SDK服务比如地图,IM等,就需要使用Service来实现,因为这些服务一般与App主进程隔离开,需要运行在新进程中以防止App主进程发生异常崩溃时,牵连第三方服务也挂掉。