Android:四大组件之 Service

简介: Service 是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务。但不要被“后台”二字迷惑,Service 默认并不会运行在子线程中,它也不会运行在一个独立的进程中,它同样执行在 UI 线程中,因此,不要在 Service 中执行耗时的操作,除非你在 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 传感器服务
相关文章
|
16天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
2月前
|
存储 Android开发 开发者
深入理解安卓应用开发的核心组件
【10月更文挑战第8天】探索Android应用开发的精髓,本文带你了解安卓核心组件的奥秘,包括Activity、Service、BroadcastReceiver和ContentProvider。我们将通过代码示例,揭示这些组件如何协同工作,构建出功能强大且响应迅速的应用程序。无论你是初学者还是资深开发者,这篇文章都将为你提供新的视角和深度知识。
|
2月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
91 0
|
2月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。
|
2月前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
48 6
|
3月前
|
存储 开发框架 数据可视化
深入解析Android应用开发中的四大核心组件
本文将探讨Android开发中的四大核心组件——Activity、Service、BroadcastReceiver和ContentProvider。我们将深入了解每个组件的定义、作用、使用方法及它们之间的交互方式,以帮助开发者更好地理解和应用这些组件,提升Android应用开发的能力和效率。
210 5
|
3月前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。
|
4月前
|
存储 搜索推荐 Java
探索安卓开发中的自定义视图:打造个性化UI组件Java中的异常处理:从基础到高级
【8月更文挑战第29天】在安卓应用的海洋中,一个独特的用户界面(UI)能让应用脱颖而出。自定义视图是实现这一目标的强大工具。本文将通过一个简单的自定义计数器视图示例,展示如何从零开始创建一个具有独特风格和功能的安卓UI组件,并讨论在此过程中涉及的设计原则、性能优化和兼容性问题。准备好让你的应用与众不同了吗?让我们开始吧!
|
4月前
|
XML 搜索推荐 Android开发
安卓开发中的自定义View组件实践
【8月更文挑战第30天】探索Android世界,自定义View是提升应用界面的关键。本文以简洁的语言带你了解如何创建自定义View,从基础到高级技巧,一步步打造个性化的UI组件。
|
4月前
|
开发工具 Android开发
Android项目架构设计问题之组件A通知组件B某个事件的发生如何解决
Android项目架构设计问题之组件A通知组件B某个事件的发生如何解决
43 0