乐趣区

关于android:Android入门教程-四大组件之Service前台服务后台服务

Service 是一种可在后盾执行长时间运行操作而不提供界面的利用组件。服务可由其余利用组件启动,而且即便用户切换到其余利用,服务仍将在后盾持续运行。

此外,组件可通过绑定到服务与之进行交互,甚至是执行过程间通信 (IPC)。例如,服务可在后盾解决网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。

前台服务

台服务执行一些用户能留神到的操作。例如,音频利用会应用前台服务来播放音频曲目。前台服务必须显示告诉。即便用户进行与利用的交互,前台服务仍会持续运行。

启动前台服务

前台服务能够给用户提供界面上的操作。每个前台服务都必须要在告诉栏显示一个告诉(notification)。用户能够感知到 app 的前台服务正在运行。这个告诉(notification)默认是不能移除的。服务进行后,告诉会被零碎移除。当用户不须要间接操作 app,app 须要给用户一个状态显示的时候,能够用前台服务。

在 activity 中启动服务,调用 startForegroundService(Intent) 办法。

startForegroundService(Intent(applicationContext, ForegroundService1::class.java))

而后在 service 中,须要对应地应用 startForeground 办法。

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {Log.d(TAG, "onStartCommand flags:$flags, startId:$startId [$this] ${Thread.currentThread()}")

        val pendingIntent: PendingIntent =
                Intent(this, ForegroundDemoAct::class.java).let { notificationIntent ->
                    PendingIntent.getActivity(this, 0, notificationIntent, 0)
                }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val chanId = "f-channel"
            val chan = NotificationChannel(chanId, "前台服务 channel",
                    NotificationManager.IMPORTANCE_NONE)
            chan.lightColor = Color.BLUE
            chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
            val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            service.createNotificationChannel(chan)
            Log.d(TAG, "服务调用 startForeground")

            val notification: Notification =
                    Notification.Builder(applicationContext, chanId)
                            .setContentTitle("RustFisher 前台服务")
                            .setContentText("https://an.rustfisher.com")
                            .setSmallIcon(R.drawable.f_zan_1)
                            .setContentIntent(pendingIntent)
                            .build()
            startForeground(1, notification)
        } else {Log.d(TAG, "${Build.VERSION.SDK_INT} < O(API 26)")
        }
        return super.onStartCommand(intent, flags, startId)
    }` 

咱们来看 service 里的这段代码。创立了一个简略的Notification

  • PendingIntent 会被调配给 Notification,作为点击告诉后的跳转动作
  • 应用 NotificationManager 先创立了一个 NotificationChannel
  • 用 Notification.Builder 配置并创立一个 Notification,例如配置题目,内容文字,图标等
  • 启动前台服务,调用 startForeground(1, notification) 办法

在设施上会显示出一个告诉, 点击这个告诉,会跳转到 ForegroundDemoAct。这是之前用 PendingIntent 设置的。

进行服务

能够用 stopService 来进行服务

stopService(Intent(applicationContext, ForegroundService1::class.java))

这样 Service 退出,走 onDestroy 办法。

进行前台服务

在 Service 中调用 stopForeground(boolean) 办法,能进行前台,然而不退出整个服务。这个 boolean 示意是否勾销掉前台服务的告诉。false 示意保留告诉。

例如在 Service 中调用

stopForeground(false)

服务变成了后盾服务,并没有退出。此时对应的告诉能够滑动勾销掉。

报错信息

ANR

在 Activity 中调用 startForegroundService(Intent) 启动服务,然而不调用Service.startForeground()。一加 5 手机 Android10 运行 log 如下

2021-08-26 23:03:25.352 25551-25551/com.rustfisher.tutorial2020 D/rustAppUseStartService: 调用 startForegroundService 主线程信息 Thread[main,5,main]
2021-08-26 23:03:25.368 25551-25551/com.rustfisher.tutorial2020 D/rustAppForeground1: onCreate Thread[main,5,main] rustfisher.com
2021-08-26 23:03:25.370 25551-25551/com.rustfisher.tutorial2020 D/rustAppForeground1: onStartCommand flags:0, startId:1 [com.rustfisher.tutorial2020.service.foreground.ForegroundService1@c77d408] Thread[main,5,main]
2021-08-26 23:03:35.375 1596-1720/? W/ActivityManager: Bringing down service while still waiting for start foreground: ServiceRecord{53d70f2 u0 com.rustfisher.tutorial2020/.service.foreground.ForegroundService1}
2021-08-26 23:03:35.382 25551-25551/com.rustfisher.tutorial2020 D/rustAppForeground1: onDestroy [com.rustfisher.tutorial2020.service.foreground.ForegroundService1@c77d408] Thread[main,5,main]

2021-08-26 23:03:52.956 1596-1720/? E/ActivityManager: ANR in com.rustfisher.tutorial2020
    PID: 25551
    Reason: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{53d70f2 u0 com.rustfisher.tutorial2020/.service.foreground.ForegroundService1}` 

Bad notification

咱们在 ForegroundService1 的办法 onStartCommand 里退出 startForeground。如果startForeground(0, noti) 的 id 传入 0,则会报错RemoteServiceException

29871-29871/com.rustfisher.tutorial2020 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.rustfisher.tutorial2020, PID: 29871
    android.app.RemoteServiceException: Bad notification for startForeground

后盾服务

后盾服务执行用户不会间接留神到的操作。例如,如果利用应用某个服务来压缩其存储空间,则此服务通常是后盾服务。

  • 文中的服务 /Service 指的是后盾服务。
  • 示例应用 Kotlin 实现。

新建服务

咱们新建一个 ServiceStartDemo 类继承 Service

class ServiceStartDemo : Service() {

    companion object {const val TAG = "rustAppStartDemoService"}

    override fun onCreate() {super.onCreate()
        Log.d(TAG, "onCreate ${Thread.currentThread()}")
    }

    override fun onBind(intent: Intent): IBinder? {Log.d(TAG, "onBind ${Thread.currentThread()}")
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {Log.d(TAG, "onStartCommand flags:$flags, startId:$startId [$this] ${Thread.currentThread()}")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {super.onDestroy()
        Log.d(TAG, "onDestroy [$this] ${Thread.currentThread()}")
    }
}
  • 在各个生命周期办法中咱们打上 log,便于后续察看
  • log 中打出服务对象的详细信息,线程信息等等
  • onBind 办法中咱们返回 null,表明这个服务不能用 bindService 的形式启动

在 AndroidManifest.xml 中注册这个服务

<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
  <!-- ... -->
  <application>
    <service
        android:name=".service.start.ServiceStartDemo"
        android:enabled="true"
        android:exported="false" />
            <!-- ... -->
  </application>
</manifest>

留神

  • name 是咱们的服务的类名。它是惟一必须的属性
  • enabled 为 true,表明零碎能够示例化这个服务。默认值为 true
  • application 也有本人的 enabled 属性,默认值为 true。该属性实用于所有利用组件,包含服务
  • exported 这里设置为 false,表明不给其余过程(app)应用,仅实用于咱们这个 app
    这样咱们的服务就筹备结束了。

startService 启动服务

在 activity 中调用 startService 办法,启动服务。

startService(Intent(applicationContext, ServiceStartDemo::class.java))

调用办法后,ServiceStartDemo 服务会启动起来。首次启动的话,服务会走 onCreate 和 onStartCommand 办法。初始化性质的代码,放在 onCreate 里。

服务曾经存在的状况下,用 startService 办法启动服务,服务会走 onStartCommand 办法。此时 onStartCommand 里的 startId 会自增。用这个数值能够看出这个 Service 对象被启动了多少次。

同时咱们能够在 Service 的 log 里察看到,Service 的生命周期函数是在主线程中执行的。因而 Service 也可能会遇到 ANR 问题。不能把过于耗时的工作放在生命周期函数里。

Activity 与 Service 沟通

Activity 与 Service 是互相独立的组件。用 startService 办法启动服务并不会让 activity 持有 service 的实例。它们之间能够用播送来进行沟通。或者用 EventBus 之类的工具进行沟通。

进行服务

实现工作后,咱们能够进行服务。节俭系统资源。后面是用 startService 办法启动的服务,前面用 stopService(Intent) 来进行服务。

办法 介绍
stopService(Intent) Activity 或其余组件调用这个办法,进行指标 service
stopSelf() Service 调用这个办法来进行本人

例如在 Activity 中

stopService(Intent(applicationContext, ServiceStartDemo::class.java))

在 Service 中

stopSelf()

一旦申请应用 stopSelf() 或 stopService() 来进行服务,服务会走 onDestroy() 办法。零碎会尽快销毁服务。

绑定服务

当利用组件通过调用 bindService() 绑定到服务时,服务即处于绑定状态。

绑定服务会提供客户端 - 服务器接口,以便组件与服务进行交互、发送申请、接管后果,甚至是利用过程间通信 (IPC) 跨过程执行这些操作。仅当与另一个利用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全副勾销绑定后,该服务即会被销毁。

Service 相干面试题

1. Service 是什么

Service 是 Android 四大组件之一,它能够在后盾执行长时间运行操作而没有用户界面的利用组件。

Service 的启动形式有两种:startService 启动和 bindService 启动。

留神:服务与其余应用程序对象一样,在其托管过程的主线程中运行。这意味着,如果你的服务要执行任何 CPU 密集型 (例如 MP3 播放) 或阻塞 (例如网络) 操作,它应该在 Service 中再创立一个子线程,而后在这里去解决耗时操作就没问题了。

2. 注册 Service 须要留神什么

Service 还是运行在主线程当中的,所以如果须要执行一些简单的逻辑操作,最好在服务的外部手动创立子线程进行解决,否则会呈现 UI 线程被阻塞的问题。

3. Service 与 Activity 怎么实现通信

办法一:

  • 增加一个继承 Binder 的外部类,并增加相应的逻辑办法
  • 重写 Service 的 onBind 办法,返回咱们刚刚定义的那个外部类实例
  • 重写 ServiceConnection,onServiceConnected 时调用逻辑办法 绑定服务

办法二

  • 通过接口 Iservice 调用 Service 办法,应用接口调用 service 和间接调用其实实质都是一样的,只不过多了借口一个步骤

4. IntentService 与 Service 的区别(intentservice 的长处)

IntentService 是 Service 的子类,是一个异步的,会主动进行的服务,很好解决了传统的 Service 中解决完耗时操作遗记进行并销毁 Service 的问题

  • 会创立独立的 worker 线程来解决所有的 Intent 申请;
  • 会创立独立的 worker 线程来解决 onHandleIntent() 办法实现的代码,无需解决多线程问题;
  • 所有申请解决实现后,IntentService 会主动进行,无需调用 stopSelf() 办法进行 Service;
  • 为 Service 的 onBind() 提供默认实现,返回 null;
  • 为 Service 的 onStartCommand 提供默认实现,将申请 Intent 增加到队列中;
  • IntentService 不会阻塞 UI 线程,而一般 Serveice 会导致 ANR 异样
  • Intentservice 若未执行实现上一次的工作,将不会新开一个线程,是期待之前的工作实现后,再执行新的工作,等工作实现后再次调用 stopSelf()

5. Service 是否在 main thread 中执行, service 外面是否 能执行耗时的操作?

默认状况, 如果没有显示的指 service 所运行的过程, Service 和 activity 是运 行在以后 app 所在过程的 main thread(UI 主线程)外面。

service 外面不能执行耗时的操作(网络申请, 拷贝数据库, 大文件)

非凡状况 , 能够在清单文件配置 service 执行所在的过程 , 让 service 在另 外的过程中执行

<service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote" >
</service>

6. Service 的生命周期

Service 有绑定模式和非绑定模式, 以及这两种模式的混合应用形式。不同 的应用办法生命周期办法也不同。

  • 非绑定模式: 当第一次调用 startService 的时候执行的办法顺次为 onCreate()、onStartCommand(), 当 Service 敞开的时候调用 onDestory 方 法。
  • 绑定模式: 第一次 bindService()的时候, 执行的办法为 onCreate()、onBind()解除绑定的时候会执行 onUnbind()、onDestory()。

下面的两种生命周期是在绝对单纯的模式下的情景。咱们在开发的过程中还 必须留神 Service 实例只会有一个, 也就是说如果以后要启动的 Service 曾经存 在了那么就不会再次创立该 Service 当然也不会调用 onCreate()办法。

一个 Service 能够被多个客户进行绑定, 只有所有的绑定对象都执行了

onBind() 办法后该 Service 才会销毁, 不过如果有一个客户执行了 onStart() 办法, 那么这个时候如果所有的 bind 客户都执行了 unBind() 该 Service 也不会 销毁。

Service 的生命周期图如下所示, 帮忙大家记忆。

只应用 startService 启动服务的生命周期

只应用 BindService 绑定服务的生命周期

同时应用 startService() 启动服务、BindService() 绑定服务的生命周期

7. Activity、Intent、Service 是什么关系

他们都是 Android 开发中应用频率最高的类。其中 Activity 和 Service 都是 Android 四大组件之一。他俩都是 Context 类的子类 ContextWrapper 的子类, 因而他俩能够算是兄弟关系吧。不过兄弟俩各有各自的本事, Activity 负责用户 界面的显示和交互, Service 负责后台任务的解决。Activity 和 Service 之间可 以通过 Intent 传递数据, 因而能够把 Intent 看作是通信使者。

8. Service 和 Activity 在同一个线程吗?

对于同一 app 来说默认状况下是在同一个线程中的,main Thread (UI Thread)。

9. 如何进步 service 的优先级?

  • AndroidManifest.xml 文件中对于 intent-filter 能够通过 android:priority =“1000” 这个属性设置最高优先级,1000 是最高值,如果数字越小则优先级越低,同时实用于播送。
  • 在 onStartCommand 外面调用 startForeground()办法把 Service 晋升为前台过程级别,而后再 onDestroy 外面要记得调用 stopForeground () 办法。
  • onStartCommand 办法,手动返回 START_STICKY。
  • 在 onDestroy 办法里发播送重启 service。service +broadcast 形式,就是当 service 走 ondestory 的时候,发送一个自定义的播送,当收到播送的时候,重新启动 service。(第三方利用或是在 setting 里 - 利用 - 强制进行时,APP 过程就间接被干掉了,onDestroy 办法都进不来,所以无奈保障会执行)
  • 监听系统播送判断 Service 状态。通过零碎的一些播送,比方:手机重启、界面唤醒、利用状态扭转等等监听并捕捉到,而后判断咱们的 Service 是否还存活。
  • Application 加上 Persistent 属性。

10. Service 的 onStartCommand 办法有几种返回值? 各代表什么意思?

有四种返回值:

  • START_STICKY:如果 service 过程被 kill 掉, 保留 service 的状态为开始状态, 但不保留递送的 intent 对象。随 后零碎会尝试从新创立 service, 因为服务状态为开始状态, 所以创立服务后肯定会调用 onStartCommand(Intent,int,int)办法。如果在此期间没有任何启动命令被传递到 service, 那么参数 Intent 将为 null。
  • START_NOT_STICKY:“非粘性的”。应用这个返回值时, 如果在执行完 onStartCommand 后, 服务被异样 kill 掉, 零碎不会主动重启该服务。
  • START_REDELIVER_INTENT:重传 Intent。应用这个返回值时, 如果在执行完 onStartCommand 后, 服务被异 常 kill 掉, 零碎会主动重启该服务, 并将 Intent 的值传入。
  • START_STICKY_COMPATIBILITY: START_STICKY 的兼容版本, 但不保障服务被 kill 后肯定能重启。

11. Activity 调用 Service 中的办法都有哪些形式?

  • Binder: 通过 Binder 接口的模式实现, 当 Activity 绑定 Service 胜利的时候 Activity 会在 ServiceConnection 的类 的 onServiceConnected()回调办法中获取到 Service 的 onBind()办法 return 过去的 Binder 的子类,而后通过对象调用办法。
  • Aidl: aidl 比拟适宜当客户端和服务端不在同一个利用下的场景。
  • Messenger: 它援用了一个 Handler 对象,以便 others 可能向它发送音讯 (应用 mMessenger.send(Message msg) 办法)。该类容许跨过程间基于 Message 的通信(即两个过程间能够通过 Message 进行通信),在服务端应用 Handler 创立一个 Messenger,客户端持有这个 Messenger 就能够与服务端通信了。一个 Messeger 不能同时双向发送,两个就就能双向发送了

12. Service 和 Thread 的区别

Service 是安卓中零碎的组件,它运行在独立过程的主线程中,不能够执行耗时操作。
Thread 是程序执行的最小单元,调配 CPU 的根本单位,能够开启子线程执行耗时操作。
Service 在不同 Activity 中能够获取本身实例,能够不便的对 Service 进行操作。
Thread 在不同的 Activity 中难以获取本身实例,如果 Activity 被销毁,Thread 实例就很难再获取失去。

13. 应用 IntentService

IntentService 是 Scrvice 的子类,因而它不是一般的 Service,它比一般的 Service 减少了额定的性能。

先看 Service 自身存在的两个问题。

  • Service 不会专门启动一个独自的过程,Service 与它所在利用位于同一个过程中。
  • Service 不是一条新的线程,因而不应该在 Service 中间接解决耗时的工作。

IntentService 正好补救了 Service 的有余。

IntentService 的特点:

  • IntentService 会创立独自的 worker 线程来解决所有的 Intent 申请。
  • IntentService 会创立独自的 worker 线程来解决 onHandleIntent()办法实现的代码,因而开发者毋庸解决多线程问题。

IntentService 实例

  1. 创立 SccIntentService.java 继承自 IntentService 类,重写 onHandleIntent() 办法、创立一个无参构造函数,其代码如下:
public class SccIntentService extends IntentService {public SccIntentService() {super("SccIntentService");
 }
 @Override
 protected void onHandleIntent(Intent intent) {MLog.e(getClass().getName(), "onHandleWork");
 for (int i = 0; i < 3; i++) {
 try {MLog.e(getClass().getName(), "Number: 开始"+i);
 Thread.sleep(10000);
 MLog.e(getClass().getName(), "Number: 完结"+i);
 } catch (InterruptedException e) {e.printStackTrace();
 }
 }
 }
 @Override
 public void onDestroy() {super.onDestroy();
 MLog.e(getClass().getName(), "onDestroy");
 }
}
  1. 增加 IntentService 组件申明,在 AndroidManifest.xml 文件中申明一个 Service 组件,其代码如下:

    <service android:name=".service.SccIntentService"/>
  2. 启动 SccIntentService

    startService(new Intent(ServiceActivity.this, SccIntentService.class));</pre>
  3. 运行后果

    07-07 18:00:39.505 E/-SCC-: com.scc.demo.actvitiy.ServiceActivityonCreate
    07-07 18:00:39.531 E/-SCC-: com.scc.demo.actvitiy.ServiceActivityonStart
    07-07 18:00:39.531 E/-SCC-: com.scc.demo.actvitiy.ServiceActivityonResume
    07-07 18:01:12.690 E/-SCC-com.scc.demo.service.SccIntentService: onHandleWork
    07-07 18:01:12.690 E/-SCC-com.scc.demo.service.SccIntentService: Number: 开始 0
    07-07 18:01:22.691 E/-SCC-com.scc.demo.service.SccIntentService: Number: 完结 0
    07-07 18:01:22.697 E/-SCC-com.scc.demo.service.SccIntentService: Number: 开始 1
    07-07 18:01:32.698 E/-SCC-com.scc.demo.service.SccIntentService: Number: 完结 1
    07-07 18:01:32.698 E/-SCC-com.scc.demo.service.SccIntentService: Number: 开始 2
    07-07 18:01:42.699 E/-SCC-com.scc.demo.service.SccIntentService: Number: 完结 2
    07-07 18:01:42.716 E/-SCC-com.scc.demo.service.SccIntentService: onDestroy

    一般 Service 间接执行 20S 的的耗时操作,会阻塞主线程,造成 ANR (程序无响应)异样。

IntentService 执行 30S 的耗时操作,不会阻塞主线程,更不会产生 ANR。如上图开始 18:01:12>18:01:42 长达 30S,失常运行未产生 ANR。

IntentService 还有个益处就是「用完即走」。执行完 onHandleIntent()办法外面的耗时操作后,自行调用 onDestroy()办法,进行敞开。

Android 入门教程视频参考

退出移动版