Android知识点 060 —— service服务

转载: 原文 https://www.jianshu.com/p/95ec2a23f300

参考文档:【1】《Android Studio开发实战从零基础到App上线》

返回知识列表 Android知识点list 


Service是Android系统中的四大组件之一,主要有两个应用场景:后台运行和跨进程访问。Service可以在后台执行长时间运行操作而不提供用户界面,除非系统必须回收内存资源,否则系统不会停止或销毁服务。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)
需要注意的是,Service是在主线程里执行操作的,可能会因为执行耗时操作而导致ANR。

一、基础知识

Service可以分为以下三种启停方式:【1】

  • 普通启停
    当应用组件通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方(因为不存在绑定和解绑定流程,所以没有binder通信)。

       启动服务依次调用了onCreate 与 onStartCommand方法。停止服务调用了onDestroy方法。

  • 立即绑定
    当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。多个组件可以同时绑定服务,服务只会在组件与其绑定时运行,一旦该服务与所有组件之间的绑定全部取消,系统便会销毁它
  • 延迟绑定
    服务既可以是启动服务,也允许绑定。此时需要同时实现以下回调方法:onStartCommand()onBind()。系统不会在所有客户端都取消绑定时销毁服务。为此,必须通过调用 stopSelf()stopService() 显式停止服务

无论应用是处于启动状态还是绑定状态,或者处于启动且绑定状态,任何应用组件均可像使用 Activity 那样通过调用 Intent 来使用服务(即使此服务来自另一应用),也可以通过清单文件将服务声明为私有服务,阻止其他应用访问

要使用服务,必须继承Service类(或者Service类的现有子类),在子类中重写某些回调方法,以处理服务生命周期的某些关键方面并提供一种机制将组件绑定到服务。Service 定义了一系列的生命周期方法,不同的启动方式会调用不同的回调方法,这里一定要注意,生命周期也不一样。

  • onStartCommand()
    当组件通过调用 startService(Intent ) 请求启动服务时,系统将调用此方法(如果是绑定服务则不会调用此方法)。一旦执行此方法,服务即会启动并可在后台无限期运行。 在指定任务完成后,通过调用 stopSelf()stopService() 来停止服务
  • onBind()
    当一个组件想通过调用 bindService() 与服务绑定时,系统将调用此方法(如果是启动服务则不会调用此方法)。在此方法的实现中,必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。
  • onRebind
  • 重新绑定。该方法只有当上次onUnbind返回true的时候才会被调用。
  • onUnbind
  • 解除绑定。返回值为true表示允许再次绑定,再绑定时调用onRebind方法;返回值为false表示只能绑定一次,不能再次绑定,默认为false。
  • onCreate()
    首次创建服务时,系统将调用此方法来执行初始化操作(在调用 onStartCommand()onBind() 之前)。如果在启动或绑定之前Service已在运行,则不会调用此方法
  • onDestroy()
    当服务不再使用且将被销毁时,系统将调用此方法,这是服务接收的最后一个调用,在此方法中应清理占用的资源

仅当内存过低必须回收系统资源以供前台 Activity 使用时,系统才会强制停止服务。

如果将服务绑定到前台 Activity,则它不太可能会终止,如果将服务声明为在前台运行,则它几乎永远不会终止。

或者,如果服务已启动并要长时间运行,则系统会随着时间的推移降低服务在后台任务列表中的位置,而服务也将随之变得非常容易被终止。

如果服务是启动服务,则必须将其设计为能够妥善处理系统对它的重启。

如果系统终止服务,那么一旦资源变得再次可用,系统便会重启服务(这还取决于 onStartCommand() 的返回值)

二、声明Service

如同其他组件一样,想要使用Service,必须在清单文件中对其进行声明。
声明方式是添加 < service > 元素作为 < application > 元素的子元素
例如

  <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        <service android:name=".MyService" />
        
    </application>

android:name 属性是唯一必需的属性,用于指定服务的类名,还可将其他属性包括在 < service > 元素中以定义一些特性。

为了确保应用的安全性,最好始终使用显式 Intent 启动或绑定 Service,且不要为服务声明 Intent 过滤器。 启动哪个服务存在一定的不确定性,而如果对这种不确定性的考量非常有必要,则可为服务提供 Intent 过滤器并从 Intent 中排除相应的组件名称,但随后必须使用 setPackage() 方法设置 Intent 的软件包,这样可以充分消除目标服务的不确定性

此外,还可以通过添加 android:exported 属性并将其设置为 "false",确保服务仅适用于本应用。这可以有效阻止其他应用启动本应用内的服务,即便在使用显式 Intent 时也是如此

Service包含的属性有

<service android:description="string resource"
         android:directBootAware=["true" | "false"]
         android:enabled=["true" | "false"]
         android:exported=["true" | "false"]
         android:icon="drawable resource"
         android:isolatedProcess=["true" | "false"]
         android:label="string resource"
         android:name="string"
         android:permission="string"
         android:process="string" >
</service>
属性说明
description对服务进行描述,属性值应为对字符串资源的引用,以便进行本地化
directBootAware设置是否可以在用户解锁设备之前运行,默认值为“false”
enabled

设置是否可以由系统来实例化服务。< application >元素有自己的enabled属性,适用于包括服务在内的所有应用程序组件。要启用服务,< application >和< service >属性必须都为“true”(默认情况下都为true)。如果其中一个是“false”,则服务被禁用

exported

    设置其他应用程序的组件是否可以调用本服务或与其交互,如果可以,则为“true”。当值为“false”时,只有同一个应用程序或具有相同用户ID的应用程序的组件可以启动该服务或绑定到该服务。

    该属性的默认值取决于服务是否包含Intent filters。没有任何过滤器意味着它只能通过指定其确切的类名来调用,这意味着该服务仅用于应用程序内部使用(因为其他人不知道类名)。所以在这种情况下,默认值为“false”。 另一方面,如果存在至少一个过滤器,意味着该服务打算供外部使用,因此默认值为“true”

icon服务的图标,属性值应是对drawable资源的引用。如果未设置,则将使用应用程序图标
isolatedProcess设置该服务是否作为一个单独的进程运行,如果设置为true,此服务将在与系统其余部分隔离的特殊进程下运行,并且没有自己的权限,与它唯一的通信是通过服务API(绑定和启动)
label可以向用户显示的服务的名称,属性值应是对字符串资源的引用
name服务类的完全限定名
permission

设定组件必须具有的权限,得以启动服务或绑定服务。如果startService(),bindService()或stopService()的调用者没有被授予此权限,则该方法将不会工作,并且Intent对象不会传递到服务中

process

用来运行服务的进程的名称。通常,应用程序的所有组件都运行在应用程序创建的默认进程中,它与应用程序包名具有相同的名称。 < application >元素的process属性可以为所有组件设置不同的默认值,但组件可以使用自己的进程属性覆盖默认值,从而允许跨多个进程扩展应用程序

三、启动Service

启动服务由组件通过调用 startService() 启动,服务启动之后,其生命周期即独立于启动它的组件,并且可以在后台无限期地运行,(这个说法只能在Android 8.0之前了,8.0之后,当APP退出后,AM会杀死还在后台的服务。如果这个服务没有在白名单里面。Stopping service due to app idle: u0a309 -1m19s437ms xxx.xxx.xxx/xxx.xxxx.xxxx)

即使启动服务的组件已被销毁也不受影响。因此,服务应通过调用 stopSelf() 来自行停止运行,或者由另一个组件调用 stopService() 来停止

可以通过扩展两个类来创建启动服务:

  • Service
    这是所有服务的父类。扩展此类时,如果要执行耗时操作,必须创建一个用于执行操作的新线程,因为默认情况下服务将运行于UI线程
  • IntentService
    这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果应用不需要同时处理多个请求,这是最好的选择。IntentService只需实现构造函数onHandleIntent() 方法即可,onHandleIntent()方法会接收每个启动请求的 Intent

3.1、继承Service

这里举一个音乐播放器的例子
继承Service类实现自定义Service,提供在后台播放音乐、暂停音乐、停止音乐的方法

public class MyService extends Service {

    private final String TAG = "MyService";

    private MediaPlayer mediaPlayer;

    private int startId;

    public enum Control {
        PLAY, PAUSE, STOP
    }

    public MyService() {
    }

    @Override
    public void onCreate() {
        if (mediaPlayer == null) {
            mediaPlayer = MediaPlayer.create(this, R.raw.music);
            mediaPlayer.setLooping(false);
        }
        Log.e(TAG, "onCreate");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        this.startId = startId;
        Log.e(TAG, "onStartCommand---startId: " + startId);
        Bundle bundle = intent.getExtras();
        if (bundle != null) {
            Control control = (Control) bundle.getSerializable("Key");
            if (control != null) {
                switch (control) {
                    case PLAY:
                        play();
                        break;
                    case PAUSE:
                        pause();
                        break;
                    case STOP:
                        stop();
                        break;
                }
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.e(TAG, "onDestroy");
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
        }
        super.onDestroy();
    }

    private void play() {
        if (!mediaPlayer.isPlaying()) {
            mediaPlayer.start();
        }
    }

    private void pause() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        }
    }

    private void stop() {
        if (mediaPlayer != null) {
            mediaPlayer.stop();
        }
        stopSelf(startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind");
        throw new UnsupportedOperationException("Not yet implemented");
    }

}

在布局中添加三个按钮,用于控制音乐播放、暂停与停止。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void playMusic(View view) {
        Intent intent = new Intent(this, MyService.class);
        Bundle bundle = new Bundle();
        bundle.putSerializable("Key", MyService.Control.PLAY);
        intent.putExtras(bundle);
        startService(intent);
    }

    public void pauseMusic(View view) {
        Intent intent = new Intent(this, MyService.class);
        Bundle bundle = new Bundle();
        bundle.putSerializable("Key", MyService.Control.PAUSE);
        intent.putExtras(bundle);
        startService(intent);
    }

    public void stopMusic(View view) {
        Intent intent = new Intent(this, MyService.class);
        Bundle bundle = new Bundle();
        bundle.putSerializable("Key", MyService.Control.STOP);
        intent.putExtras(bundle);
        startService(intent);
        //或者是直接如下调用
        //Intent intent = new Intent(this, MyService.class);
        //stopService(intent);
    }
    
}

在清单文件中声明Service,为其添加label标签,便于在系统中识别Service

      <service
          android:name=".MyService"
          android:label="@string/app_name" />

点击“播放音乐”按钮后,在后台将会运行着名为“Service测试”的服务

通过Log日志可以发现,多次点击“播放音乐”按钮,“onCreate()”方法只会在初始时调用一次,“onStartCommand(Intent intent, int flags, int startId)”方法会在每次点击时都被调用,点击“停止音乐”按钮,“onDestroy()”方法会被调用

当中,每次回调onStartCommand()方法时,参数“startId”的值都是递增的,startId用于唯一标识每次对Service发起的处理请求
如果服务同时处理多个 onStartCommand() 请求,则不应在处理完一个启动请求之后立即销毁服务,因为此时可能已经收到了新的启动请求,在第一个请求结束时停止服务会导致第二个请求被终止。为了避免这一问题,可以使用 stopSelf(int) 确保服务停止请求始终基于最新一次的启动请求。也就是说,如果调用 stopSelf(int) 方法的参数值与onStartCommand()接受到的最新的startId值不相符的话,stopSelf()方法就会失效,从而避免终止尚未处理的请求

如果服务没有提供绑定,则使用 startService() 传递的 Intent 是应用组件与服务之间唯一的通信模式。如果希望服务返回结果,则启动服务的客户端可以为广播创建一个 PendingIntent (使用 getBroadcast()),并通过启动服务的 Intent 传递给服务。然后,服务就可以使用广播传递结果

当中,onStartCommand() 方法必须返回一个整数,用于描述系统应该如何应对服务被杀死的情况,返回值必须是以下常量之一: 以下常量定义在 android/app/Service.java 

返回值类型返回值说明
START_STICKY = 1默认是这个 super.onStartCommand(intent, falgs, startId)
如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)
START_NOT_STICKY = 2如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务
START_REDELIVER_INTENT = 3如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务
STRAT_STICKY_COMPATIBILITYSTART_STICKY的兼容版本,不保证服务被杀掉后一定能重启。

 


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ricardo于

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值