阅读 97

Android 基础知识2:四大组件之 Service

目录

目录

一、Service 的定义

Service 是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。但不要被“后台”二字迷惑,Service 默认并不会运行在子线程中,它也不会运行在一个独立的进程中,它同样执行在 UI 线程中,因此,不要在 Service 中执行耗时的操作,除非你在 Service 中创建子线程来完成耗时操作。

Service 的运行不依赖于任何用户界面,即使程序被切换到后台或者用户打开了另一个应用程序,Service 仍然能够保持正常运行,这也正是 Service 的使用场景。当某个应用程序进程被杀掉时,所有依赖于该进程的 Service 也会停止运行。

二、Service 的分类

(一)根据启动方式将 Service 分为:启动服务 Started Service 和绑定服务 Bound Service。

  • Started Service:被启动的服务 被启动的服务是由其它组件调用 startService() 方法而启动的,该方法会导致被启动服务的生命周期方法onStartCommand() 被回调。当服务是被启动状态后,其生命周期与启动它的组件无关,即使启动服务的组件(Activity,BroadcastReceiver)已经被销毁,该服务还可以在后台无限期运行。除非调用 stopSelf() 或stopService() 来停止该服务。

  • Bound Service:被绑定的服务 绑定服务是允许其它应用程序绑定并且与之交互的 Service 的实现类。为了提供绑定,必须实现 onBind() 回调方法。该方法返回 IBinder 对象,它定义了服务类与Activity交互的程序接口。Activity 通过 bindService() 方法绑定到服务类,同时 Activity 必须提供 ServiceConnection 接口的实现类,它监视 Activity 与服务类之间的连接。在重写 ServiceConnection 接口的 onServiceConnected() 方法时,实现了将服务类顺利赋值到了Activity 中,实现了在 Activity 中使用该服务类并执行其中的方法。

(二)根据 onStartCommand() 回调方法的返回值,将 Service 分为粘性 Service非粘性 Service: onStartCommand() 方法有三种返回值:

  1. START_STICKY(常量值:1):sticky的意思是“粘性的”。使用这个返回值时,我们启动的服务跟应用程序"粘"在一起,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务。当再次启动服务时,传入的第一个参数将为null;

  2. START_NOT_STICKY(常量值:2):“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务;

  3. START_REDELIVER_INTENT(常量值:3):重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。

以上三种情况,可以理解为发生车祸后的人:

  • START_STICKY:(常量值:1)车祸后自己苏醒,但是失忆;
  • START_NOT_STICKY:(常量值:2)车祸后再也没有苏醒;
  • START_REDELIVER_INTENT:(常量值:3)车祸后自己苏醒,依然保持记忆。

(三)根据 Service 的使用范围还可以将其分为:本地服务 Local Service 和 远程服务 Remote Service。

  • 本地服务 Local Service Local Service 用于应用程序内部。用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如 Activity 所属线程,而是单开线程后台执行,这样用户体验比较好。

  • 远程服务 Remote Service Remote Service 用于 Android 系统内部的应用程序之间。可以定义接口并把接口暴露出来,以便其他应用进行操作。可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。

三、Service 的生命周期

Service 的生命周期

(一)Started Service 的生命周期解析:

  • onCreate():创建服务

  • onStartCommand():服务开始运行(在 2.0 以前版本中,使用 onStart() 回调方法)

  • onDestroy():服务被停止

详细说明

  • 在程序中调用:context.startService() 会触发执行 Service 生命周期中的 onCreate()、onStartCommand() 回调方法,此时服务就开始正式运行;

  • 如果 Service 还没有运行,则 Android 先调用 onCreate() 然后调用 onStartCommand();

  • 如果 Service 已经运行,则只调用 onStartCommand(),所以一个 Service 的 onStartCommand 方法可能会重复调用多次;

  • 如果在程序中调用:context.stopService() 会触发执行 Service 生命周期中的 onDestroy() 回调方法,会让服务停止;

  • stopService() 的时候直接 onDestroy,如果是调用者自己直接退出而没有调用 stopService() 的话,Service会一直在后台运行。该 Service 的调用者再启动该 Service 后可以通过 stopService 关闭 Service;

  • 所以StartService 的生命周期为:onCreate -- onStartCommand(可多次调用) -- onDestroy。

(二)Bound Service 的生命周期解析:

  • onCreate():创建服务

  • onBind():绑定服务,服务开始运行

  • onUnbind():取消绑定

  • onDestroy() :服务被停止

详细说明

  • 在程序中调用:context.bindService() 会触发执行 Service 生命周期中的 onCreate()、onBind() 回调方法,此时服务开始运行;

  • onBind 将返回给客户端一个 IBind 接口实例,IBind 允许客户端回调服务的方法,比如得到 Service 运行的状态或其他操作。此后调用者(Context,例如Activity)会和 Service 绑定在一起;

  • 如果调用 Service 的调用者 Context 退出了,那么会依次调用 Service 生命周期中的 onUnbind()、onDestroy() 回调方法,会让服务停止;

  • 所以 BindService 的生命周期为:onCreate -- onBind(只一次,不可多次绑定) -- onUnbind -- onDestory。

备注

  1. Service 是不能自己启动的,只有通过 Context 对象调用 startService() 和 bindService() 方法来启动。 在 Service 每一次的开启关闭过程中,只有 onStartCommand() 可被多次调用(通过多次 startService 调用),其他 onCreate()、onBind()、onUnbind()、onDestory() 在一个生命周期中只能被调用一次。

  2. Service 可以在和多场合的应用中使用,比如播放多媒体的时候用户启动了其他 Activity 这个时候程序要在后台继续播放,比如检测 SD 卡上文件的变化,再或者在后台记录你地理信息位置的改变等等,总之服务总是藏在后头的。

(三)简单的代码示例

public class MyService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // do something
        return super.onStartCommand(intent, flags, startId);
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}
复制代码

与 Activity 一样,Service 也需要在 AndroidManifest.xml 中进行注册:

<service android:name=".MyService" />
复制代码

四、IntentService

由于 Service 默认运行在主线程,所以如果运行非常耗时的操作时,可能会出现 ANR 的错误。当然,可以在 Service 中开启一个工作线程来解决,但是这样做非常的麻烦。Android 为了省去这样的麻烦,提供了一个 IntentService 来完成这样的操作, IntentService 将用户的请求执行在一个子线程中,用户只需要覆写 onHandleIntent() 函数,并且在该函数中完成自己的耗时操作即可。同时,在任务执行完毕之后 IntentService 会调用 stopSelf 自我销毁。因此,它适合于完成一些短期的耗时操作。示例代码如下:

public class MyIntentService extends IntentService {

    public MyIntentService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        //do something
    }
}
复制代码

Android 官方对于 IntentService 的说明:

  • Creates a default worker thread that executes all intents delivered to onStartCommand() separate from your application's main thread. 生成一个默认的且与主线程互相独立的工作者线程来执行所有传送至 onStartCommand() 方法的 Intetnt

  • Creates a work queue that passes one intent at a time to your onHandleIntent() implementation, so you never have to worry about multi-threading. 生成一个工作队列来传送Intent对象给你的onHandleIntent()方法,同一时刻 只传送一个Intent对象,这样一来,你就不必担心多线程的问题。

  • Stops the service after all start requests have been handled, so you never have to call stopSelf(). 在所有的请求(Intent)都被执行完以后会自动停止服务,所以,你不需要自己去调用stopSelf()方法来停止该服务

  • Provides default implementation of onBind() that returns null. 提供了一个onBind()方法的默认实现,它返回null

  • Provides a default implementation of onStartCommand() that sends the intent to the work queue and then to your onHandleIntent() implementation 提供了一个onStartCommand()方法的默认实现,它将Intent先传送至工作队列,然后从工作队列中每次取出一个传送至onHandleIntent()方法,在该方法中对Intent对相应的处理

IntentService 使用队列的方式将请求的 Intent 加入队列,然后开启一个 worker thread (线程)来处理队列中的 Intent,对于异步的 startService 请求,IntentService 会处理完成一个之后再处理第二个,每一个请求都会在一个单独的 worker thread 中处理,不会阻塞应用程序的主线程。

五、运行在前台的 Service

Service 默认是运行在后台的,因此,它的优先级相对比较低,当系统出现内存不足的时候,它就有可能被系统回收。如果希望 Service 可以一直保持运行状态,而不会由于系统内存不足被回收,可以将 Service 运行在前台。前台服务不仅不会被回收,它还会在通知栏显示一条消息,下拉状态栏后可以看到更加详细的信息。

public class WeatherService extends Service {
    private static final int NOTIFY_ID = 123;
    private static final String CHANNEL_ONE_ID = "com.cyy.cn";
    private static final String CHANNEL_ONE_NAME = "Channel One";

    @Override
    public void onCreate() {
        super.onCreate();
        showNotification();
    }

    /**
     * 在通知栏显示天气信息
     */
    private void showNotification() {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setContentTitle(getText(R.string.the_day))
                .setContentText(getText(R.string.weather));

        Intent intent = new Intent(this, MainActivity.class);

        //创建任务栈
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(MainActivity.class);
        stackBuilder.addNextIntent(intent);

        PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(resultPendingIntent);

        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        //android 8.0 后需要给 notification 设置一个 channelId
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ONE_ID,
                    CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_HIGH);
            notificationChannel.enableLights(true);
            notificationChannel.setLightColor(Color.RED);
            notificationChannel.setShowBadge(true);
            notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
            notificationManager.createNotificationChannel(notificationChannel);
            builder.setChannelId(CHANNEL_ONE_ID);
        }

        Notification notification = builder.build();
        notificationManager.notify(NOTIFY_ID, notification);
        startForeground(NOTIFY_ID, notification);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
复制代码

我们通过调用 startForeground 将服务设置成前台服务。同时也需要在 AndroidManifest.xml 中进行注册。

启动该服务:

Intent intent = new Intent();
intent.setClass(MainActivity.this, WeatherService.class);
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    //android 8.0 以上通过 startForegroundService 启动 Service
    startForegroundService(intent);
  } else {
    startService(intent);
  }
复制代码

运行效果:

补充知识点:Android进程优先级

Android操作系统尝试尽可能长时间保持应用的进程,但当可用内存很低时要移走一部分进程。哪些程序可以运行,哪些要被销毁?答案是:重要级别低的进程可能被淘汰。

按照重要性排列,一共可以分成5级:

1.前台运行进程 用户此时需要处理和显示的进程。符合下列条件任何一个,这个进程就被认为是前台运行进程:

  • 与用户正发生交互;

  • 它控制一个与用户交互的必须的基本的服务;

  • 一个正在调用生命周期回调方法的 Service(如onCreate()、onStar()、onDestroy());

  • 一个正在运行 onReceive() 方法的广播接收对象。

销毁前台运行进程是系统万不得已的、最后的选择——当内存不够系统继续运行下去时,杀掉一些前台进程来保证能够响应用户的需求。

2.可见进程 能被用户看到,但不能根据根据用户的动作做出相应的反馈,

3.服务进程 服务进程是一个通过调用 startService() 方法启动的服务,并且不属于前两种情况。尽管服务进程没有直接被用户看到,但他们确实是用户所关心的,比如后台播放音乐或网络下载数据,所以系统保证他们的运行。

4.后台进程 一个后台进程就是非当前正在运行的 Activity(Activity 的 onStop() 方法已经被调用),他们不会对用户体验造成直接的影响,当没有足够内存来运行前台可见程序时,他们将会被终止。

通常,后台进程会有很多个在运行,LRU 最近使用程序列表来保证经常运行的 Activity 能最后一个被终止。

5.空进程 一个空进程没有运行任何可用应用程序,保留他们的唯一原因是为了设立一个缓存机制,来加快组件启动的时间。系统经常杀死这些内存来平衡系统的整个系统的资源,进程缓存和基本核心缓存之间的资源。

六、Android 系统级别的 Service

getSystemService(String name) 是 Android 很重要的一个方法,根据 NAME 来取得对应的 Object,然后转换成相应的服务对象,例如:

LayoutInflater layoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
复制代码

以下介绍系统相应的服务:

name 返回的对象 管理打开的窗口程序
WINDOW_SERVICE WindowManager 管理打开的窗口程序
LAYOUT_INFLATER_SERVICE LayoutInflater 取得xml里定义的view
ACTIVITY_SERVICE ActivityManager 管理应用程序的系统状态
POWER_SERVICE PowerManger 电源的服务
ALARM_SERVICE AlarmManager 闹钟的服务
NOTIFICATION_SERVICE NotificationManager 状态栏的服务
KEYGUARD_SERVICE KeyguardManager 键盘锁的服务
LOCATION_SERVICE LocationManager 位置的服务,如GPS
SEARCH_SERVICE SearchManager 搜索的服务
VIBRATOR_SERVICE Vibrator 手机震动的服务
CONNECTIVITY_SERVICE Connectivity 网络连接的服务
WIFI_SERVICE WifiManager Wi-Fi服务
TELEPHONY_SERVICE TeleponyManager 电话服务
SENSOR_SERVICE SensorManager 传感器服务

参考资料:

  1. 《Android开发进阶从小工到专家》何红辉
  2. Android进程优先级
关注下面的标签,发现更多相似文章
评论