四大组件之Service

参考:Android 四大组件

参考:Android Service组件深入解析

Service 简介

Service 是 Android 中实现程序后台运行的解决方案,非常适合于去执行那些不需要和用户交互而且还要求长期运行的任务。不能运行在一个独立的进程中,而是依赖与创建服务时所在的应用程序进程。Service 不会自动开启线程,默认运行在主进程的 main 线程中,所以不能进行耗时操作,可以采用在 Service 里面开启一个新的线程来执行任务。只能在后台运行,可以和其他组件进行交互。

Service 启动

start 方式

操作步骤:

  1. 定义一个类继承 Service

  2. 清单文件 manifest.xml 中配置 Service

  3. 使用 context 的 startService(Intent) 方法启动 Service

  4. 不再使用时,调用 context 的 stopService(Intent) 方法停止服务

示例如下:

第一步:

public class TestService extends Service {

    protected final String TAG = this.getClass().getSimpleName();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Logger.i(" onCreate: " + TAG + " === " + " hasCode: " + hashCode());

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logger.i(" onStartCommand: " + TAG + " === " + " hasCode: " + hashCode());
        return super.onStartCommand(intent, flags, startId);
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        Logger.i(" onDestroy: " + TAG + " === " + " hasCode: " + hashCode());

    }
}

第二步:

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

第三步:

startService(new Intent(this, TestService.class));

第四步:

stopService(new Intent(this, TestService.class));

案例操作及分析:先启动服务,然后再点击启动同一个服务,最后停止服务。日志如下:

这种方式的 Service 的生命周期为 onCreate() -> onStartCommand() -> onDestory() ,由日志可知,首次启动服务,会回调 onCreate() -> onStartCommand() ,重复启动服务,不再回调 onCreate() 方法,只回调 onStartCommand() 方法,销毁时回调 onDestory() 方法。

总结:服务对象同时只会存在一个,Service 与启动它的组件在同一个线程中。上面的实例中,服务就是在 main 线程中运行的,如果是在服务中完成耗时操作的话,容易造成主线程阻塞。

特点:
服务开启后,就跟调用者没有任何关系了,调用者的存活不会影响服务的存活,调用者不能调用服务里面的方法。

停止一个 started 服务有两种方法:

(1)在外部使用stopService()

(2)在服务内部(onStartCommand方法内部)使用 stopSelf() 方法。

IntentService

为了可以简单地创建一个异步的、会自动停止的服务,Android 专门提供了一个 IntentService 类,可以启动 IntentService 多次,而每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent() 回调方法中执行,并且每次只会执行一个工作线程,执行完第一个后,再执行第二个,以此类推。

使用时机:当我们需要这样一次性完成的任务时,就可以使用 IntentService 来完成。

案例操作及分析:先启动服务,然后再点击启动同一个服务

由日志可知, onHandleIntent() 方法开启了新的工作线程,多次启动服务时,会以工作队列的方式在该线程依次处理,处理完了之后会自动停止服务,无需调用 stopService() 或 stopSelf() 方法停止。

bind 方式

通过调用 bindService() 方法能够绑定服务,然后系统会调用服务的 onBind() 回调方法,这个方法会返回一个跟服务器端交互的 Binder 对象。但是这个绑定是异步的,要接收 IBinder 对象,客户端必须创建一个 ServiceConnection 类的实例,并且把这个实例传递给bindService() 方法。 ServiceConnection 对象包含了一个系统调用的传递 IBinder 对象的回调方法。

操作步骤:

  1. 定义一个类继承 Service

  2. 清单文件 manifest.xml 中配置 Service

  3. 使用 context 的 bindService(Intent,ServiceConnection,int) 方法启动 Service

  4. 不再使用时,调用 context 的 unbindService(ServiceConnection) 方法停止服务

这种方式的生命周期如下:

onCreate() -> onBind() -> onUnbind() -> onDestory()

注意:不会回调 onStartCommand() 方法

特点: 调用方的存活会决定服务的存活,如果调用方挂了,服务也会挂掉,调用方可以跟服务进行关联,调用服务里的方法。

第一步:

public class BindService extends Service {

    protected final String TAG = this.getClass().getSimpleName();

    private MyBinder mBinder = new MyBinder();

    public class MyBinder extends Binder {

        @Override
        public String toString() {
            return "this is mybinder";
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Logger.i(" onBind: " + TAG + " === " + " ThreadId: "+ Thread.currentThread().getId()+ " === " +" hasCode: " + hashCode());

        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Logger.i(" onCreate: " + TAG + " === " + " ThreadId: "+ Thread.currentThread().getId()+ " === " +" hasCode: " + hashCode());

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logger.i(" onStartCommand: " + TAG + " === " + " ThreadId: "+ Thread.currentThread().getId()+ " === " +" hasCode: " + hashCode());

        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Logger.i(" onUnbind: " + TAG + " === " + " ThreadId: "+ Thread.currentThread().getId()+ " === " +" hasCode: " + hashCode());
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Logger.i(" onDestroy: " + TAG + " === " + " ThreadId: "+ Thread.currentThread().getId()+ " === " +" hasCode: " + hashCode());

    }
}

第二步:

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

第三步:

  mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                     //服务连接成功
                mBinder = (BindService.MyBinder) service;
                Logger.i(" onServiceConnected: " + mBinder.toString());

            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                    //与服务连接断连,异常终止时会调用。解除绑定服务时不会调用
            }
        };

bindService(new Intent(this, BindService.class),mServiceConnection,BIND_AUTO_CREATE);

第四步:

unbindService(mServiceConnection);

以上这两种销毁的方式都很好理解。那么如果我们既点击了 Start Service 按钮,又点击了 Bind Service 按钮会怎么样呢?这个时候你会发现,不管你是单独点击 Stop Service 按钮还是 Unbind Service 按钮,Service 都不会被销毁,必需将 Unbind Service 按钮和 Stop Service 按钮都点击一下(没有先后顺序),Service 才会被销毁。也就是说,点击 Stop Service 按钮只会让 Service 停止,点击 Unbind Service 按钮只会让 Service 和 Activity 解除关联,一个 Service 必须要在既没有和任何 Activity 关联又处于停止状态的时候才会被销毁,即回调 onDestroy() 方法。我们细说一下这种情况的生命周期。

情况一:start - bind

生命周期:onCreate -> onStartCommand -> onBind -> onUnbind -> onDestroy

情况三:bind -start

生命周期:onCreate -> onBind -> onStartCommand -> onUnbind -> onDestroy

分析:一旦在项目的任何位置调用了 Context 的 startService() 方法,相应的服务就会启动起来,并回调 onstartCommand() 方法。如果这个服务之前还没有创建过, onCreate() 方法会先于 onstartCommand() 方法执行。服务启动过后,会一直保持运行状态,直到 stopService() 或 stopself() 方法被调用。注意虽然每次调用一次 startService() 方法, onstartCommand() 方法就会以执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次 startService() 方法,只需调用一次 stopService() 或 stopself() 方法,服务就会停止。

另外,还可以调用 Context 的 bindService() 来获取一个服务的持久连接,这时就会回调服务中的 onBind() 方法。类似地,如果这个服务之前还没有创建过, onCreate() 方法会先于 onBind() 方法执行。之后调用方可以获取到 onBind() 方法里返回的 IBinder 对象的实例,这样,就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。

注意:如果未绑定过服务或已经解绑了,点击 unbind 会报错。

开发中的技巧:可以先用 start 方式启动服务,接着用 bind 方式绑定服务,与服务进行关联进行业务处理。

Service 生命周期