Android中Service总结

1,322 阅读7分钟

众所周知,Android中的Service运行在后台,即它不依赖UI界面,所以即使开启Service的Activity退出或者被销毁,但是只要进程还存在,Service就处于运行的状态,进程被杀死,Service也就被销毁了。那么Service是如何运行的呢?Service只能被手动开启,它有两种开启方式:一种是通过context.startService(Intent intent)方法开启,另一种是通过context.bindService(Intent intent,ServiceConnection conn, int flags),这两种方式的应用场景取决于是否需要与Service进行通信,如果只是启动Service而不需要与其通信,那么使用第一种方式就可以了。但如果你的需求中需要与Service保持通信,调用Service中的方法,例如音乐播放器,那么需要使用第二种启动方式。

启动方式不同,Service中被执行的函数也不同。使用第一种方式启动Service,函数的执行流程如下:

08-20 16:44:16.619 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onCreate
08-20 16:44:16.632 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:44:16.632 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart

当多次调用context.startService(Intent intent)方法后,执行流程如下:

08-20 16:44:16.619 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onCreate
08-20 16:44:16.632 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:44:16.632 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
08-20 16:45:25.064 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:45:25.064 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
08-20 16:45:25.858 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:45:25.858 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
08-20 16:45:26.582 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:45:26.582 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart

从上面的输出可以看出,

首次调用context.startService(Intent intent)方法后Service中函数执行的顺序是onCreate()、onStartCommand(),多次调用context.startService(Intent intent)之后只会执行多次onStartCommand()方法,这里onStart()方法被onStartCommand()方法替代,多次调用context.startService(Intent intent)方法后,手动调用context.stopService(Intent intent)方法后,Service会执行如下函数:

08-20 16:45:25.858 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:45:25.858 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
08-20 16:45:26.582 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 16:45:26.582 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onStart
duo08-20 17:02:16.711 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onDestroy

再次调用context.stopService(Intent intent)方法后无效。所以多次调用context.startService(Intent intent)后,只需要调用一次context.stopService(Intent intent)方法就可以销毁Service对象。

使用第二种方式启动Service后即context.bindService(Intent intent,ServiceConnection conn,int flags)后,Service内的函数执行流程如下:

08-20 17:05:45.127 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onCreate
08-20 17:05:45.127 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onBind
08-20 17:05:45.129 9385-9385/com.example.guoyan1.rotationdemo D/MyService: onServiceConnected

多次执行该函数后以上函数不会被多次调用,该方式主要用于外界与Service进行通信,所以一般使用该方式绑定服务时,需要创建ServiceConnection实例对象,但因为ServiceConnection是接口,该接口里面有如下两个方法:

public void onServiceConnected(ComponentName name, IBinder service);

public void onServiceDisconnected(ComponentName name);

所以创建ServiceConnection实例对象时需要重写以上两个方法,其中一旦服务被绑定即执行了Service中的onBind(Intent intent)方法后,onServiceConnected方法就会被调用,该方法中的第二个参数IBinder即为外界和Service通信的通道,进而可以调用Service内的方法,那么这个IBinder又是在哪里被创建的呢?通常我们在Service中会创建一个内部类继承Binder,该Binder是Android.os包内的类,查看该类可以发现它实现了IBinder接口

public class Binder implements IBinder

恍然大悟了吧,这里的Binder其实和ServiceConnction里面的onServiceConnected方法中第二个参数IBinder是一个类型呀!通常情况下我们在Service中创建Binder的子类

public class MyBinder extends Binder {
        public void startDownLoad() {
            Log.d(TAG, "start download");
        }
    }
    

绑定服务后,Service中会执行onBind(Intent intent)方法,所以通常我们会在该方法中创建MyBinder的实例对象并作为返回值。

@Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        return new MyBinder();
    }

绑定服务成功后,该对象会作为参数传递给ServiceConnection内的onServiceConnected方法

 private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("MyService","onServiceConnected");
            if (null != service) {
                MyService.MyBinder binder = (MyService.MyBinder) service;
                binder.startDownLoad();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("MyService","onServiceDisconnectd");
        }
    };

进而达到了和Service进行通信的逻辑,一般自定义的方法都会写在MyBinder中。

如果我们同时手动调用了startServie(Intent intent)和bindService(Intent intent)方法Service中的函数执行属性是如何的呢
? 情形一:先调用startServie(Intent intent)再调用 bindService(Intent intent),Service内部的函数执行顺序如下

08-20 18:22:16.981 15690-15690/com.example.guoyan1.rotationdemo D/MyService: onCreate
08-20 18:22:16.992 15690-15690/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 18:22:16.992 15690-15690/com.example.guoyan1.rotationdemo D/MyService: onStart
08-20 18:22:20.114 15690-15690/com.example.guoyan1.rotationdemo D/MyService: onBind
08-20 18:22:20.120 15690-15690/com.example.guoyan1.rotationdemo D/MyService: onServiceConnected

那如果先调用bindService(Intent intent)再调用startServie(Intent intent)呢?

08-20 18:24:03.250 17197-17197/com.example.guoyan1.rotationdemo D/MyService: onCreate
08-20 18:24:03.261 17197-17197/com.example.guoyan1.rotationdemo D/MyService: onBind
08-20 18:24:03.262 17197-17197/com.example.guoyan1.rotationdemo D/MyService: onServiceConnected
08-20 18:24:03.262 17197-17197/com.example.guoyan1.rotationdemo D/MyService: start download
08-20 18:24:08.047 17197-17197/com.example.guoyan1.rotationdemo D/MyService: onStartCommand
08-20 18:24:08.048 17197-17197/com.example.guoyan1.rotationdemo D/MyService: onStart

综合上述两种情况可以发现首次绑定/开启服务都会首先调用onCreate方法,并且该方法只会被调用一次,如果是先调用的startService(Intent intent)那么Service内会先执行onStartCommand方法,进而执行onBind方法、onServiceConnected方法。如果是先调用的bindService(Intent intent)方法则Service内会执行onBind方法、onServiceConnecnted方法,继续执行onStartCommand方法,并且多次调用onStartService(Intent intent)方法,Service内会多次执行onStartCommand方法。

至于停止/解绑Service的方法stopService(Intent intent)和unbindService(ServiceConnection conn)的调用就很有讲究了。

仅仅手动调用了startService(Intent intent)或者bindService(Intent intent)后关闭/解绑Service的方法是手动调用相应的stopService(Intent intent)/unbindServie(ServiceConnection conn),Service内部会执行onDestroy方法。

如果手动调用startService(Intent intent)和bindService(Intent intent)后,继续调用unbindService的话,Service内部会执行onUnbind方法,但是此时Service还处于运行的状态,此时必须调用stopService(Intent intent)之后,Service内部才会执行onDestroy方法。

如果你没有开启/绑定服务,手动调用stopService(Intent intent)不会有任何反应,但是该情况下调用unBindService(ServiceConnection conn)会crash,报错如下:

Caused by: java.lang.IllegalArgumentException: Service not registered: com.example.guoyan1.rotationdemo.SixthActivity$1@bd0ebc4
                                                                                      at android.app.LoadedApk.forgetServiceDispatcher(LoadedApk.java:1477)
                                                                                      at android.app.ContextImpl.unbindService(ContextImpl.java:1632)
                                                                                      at android.content.ContextWrapper.unbindService(ContextWrapper.java:707)

报错的大致内容是Service还没有注册呢,所以解绑异常,需要注意的是注册Service途径是手动调用bindService方法,因为实验发现即便手动调用startService(Intent intent)方法后,再手动调用unbindService(ServiceConnection conn)后还会出现crash,报错同上,并且多次调用bindService后Service中的onBind方法仅会执行一次,并且unbindService(ServiceConnection conn)只能调用一次,否则会报如上错误。

Service和Thread的区别

Service是运行在主线程的,只是Service的运行不依赖任何的UI界面,可以使用context.startService(Intent intent)/contenxt.bindServie(Intent intent,ServiceConnection conn,int flags)开启误/绑定服务,只不过使用前者开启服务后,开启服务的对象不能与服务通信。而是用后者绑定服务后可以获取服务中的Binder对象进而与其进行通信,所以哪个对象想和Service通信只需要调用context.bindService()方法就可以啦,并且Service是依赖进程的,只要进程存在Service就会处于运行的状态。但是Thread不同,首先Thread不依赖进程,其次创建该Thread的对象可以对其进行控制,一旦创建它的对象被销毁,Thread就处于完全不可控的状态,因为其他任何对象对不能操作该Thread。 好了今天的总结就到这里吧!明天继续。