Android入门教程 | 四大组件之Service(前台服务,后台服务)

Android入门教程 | 四大组件之Service(前台服务,后台服务) Service是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动而且即使用户切换到其他应用服务仍将在后台继续运行。此外组件可通过绑定到服务与之进行交互甚至是执行进程间通信 (IPC)。 例如服务可在后台处理网络事务、播放音乐执行文件 I/O 或与内容提供程序进行交互。前台服务台服务执行一些用户能注意到的操作。例如音频应用会使用前台服务来播放音频曲目。前台服务必须显示通知。 即使用户停止与应用的交互前台服务仍会继续运行。启动前台服务前台服务可以给用户提供界面上的操作。 每个前台服务都必须要在通知栏显示一个通知notification。用户可以感知到app的前台服务正在运行。 这个通知notification默认是不能移除的。服务停止后通知会被系统移除。 当用户不需要直接操作appapp需要给用户一个状态显示的时候可以用前台服务。在 activity 中启动服务调用startForegroundService(Intent)方法。startForegroundService(Intent(applicationContext,ForegroundService1::class.java))然后在 service 中需要对应地使用startForeground方法。override funonStartCommand(intent:Intent?,flags:Int,startId:Int):Int{Log.d(TAG,onStartCommand flags:$flags, startId:$startId [$this] ${Thread.currentThread()})val pendingIntent:PendingIntentIntent(this,ForegroundDemoAct::class.java).let{notificationIntent-PendingIntent.getActivity(this,0,notificationIntent,0)}if(Build.VERSION.SDK_INTBuild.VERSION_CODES.O){val chanIdf-channelval chanNotificationChannel(chanId,前台服务channel,NotificationManager.IMPORTANCE_NONE)chan.lightColorColor.BLUEchan.lockscreenVisibilityNotification.VISIBILITY_PRIVATEval servicegetSystemService(Context.NOTIFICATION_SERVICE)asNotificationManager service.createNotificationChannel(chan)Log.d(TAG,服务调用startForeground)val notification:NotificationNotification.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) )}returnsuper.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-2623:03:25.35225551-25551/com.rustfisher.tutorial2020D/rustAppUseStartService:调用 startForegroundService 主线程信息Thread[main,5,main]2021-08-2623:03:25.36825551-25551/com.rustfisher.tutorial2020D/rustAppForeground1:onCreate Thread[main,5,main]rustfisher.com2021-08-2623:03:25.37025551-25551/com.rustfisher.tutorial2020D/rustAppForeground1:onStartCommand flags:0,startId:1[com.rustfisher.tutorial2020.service.foreground.ForegroundService1c77d408]Thread[main,5,main]2021-08-2623:03:35.3751596-1720/?W/ActivityManager:Bringing down servicewhilestill waitingforstart foreground:ServiceRecord{53d70f2 u0 com.rustfisher.tutorial2020/.service.foreground.ForegroundService1}2021-08-2623:03:35.38225551-25551/com.rustfisher.tutorial2020D/rustAppForeground1:onDestroy[com.rustfisher.tutorial2020.service.foreground.ForegroundService1c77d408]Thread[main,5,main]2021-08-2623:03:52.9561596-1720/?E/ActivityManager:ANRincom.rustfisher.tutorial2020PID:25551Reason: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.tutorial2020E/AndroidRuntime:FATALEXCEPTION:mainProcess:com.rustfisher.tutorial2020,PID:29871android.app.RemoteServiceException:Bad notificationforstartForeground后台服务后台服务执行用户不会直接注意到的操作。例如如果应用使用某个服务来压缩其存储空间则此服务通常是后台服务。文中的服务/Service 指的是后台服务。示例使用Kotlin实现。新建服务我们新建一个 ServiceStartDemo 类继承 ServiceclassServiceStartDemo:Service(){companion object{constvalTAGrustAppStartDemoService}override funonCreate(){super.onCreate()Log.d(TAG,onCreate ${Thread.currentThread()})}override funonBind(intent:Intent):IBinder?{Log.d(TAG,onBind ${Thread.currentThread()})returnnull}override funonStartCommand(intent:Intent?,flags:Int,startId:Int):Int{Log.d(TAG,onStartCommand flags:$flags, startId:$startId [$this] ${Thread.currentThread()})returnsuper.onStartCommand(intent,flags,startId)}override funonDestroy(){super.onDestroy()Log.d(TAG,onDestroy [$this] ${Thread.currentThread()})}}在各个生命周期方法中我们打上log便于后续观察log 中打出服务对象的详细信息线程信息等等onBind 方法中我们返回null表明这个服务不能用bindService的方式启动在 AndroidManifest.xml 中注册这个服务manifest xmlns:androidhttp://schemas.android.com/apk/res/android!--...--applicationserviceandroid:name.service.start.ServiceStartDemoandroid:enabledtrueandroid:exportedfalse/!--...--/application/manifest注意name 是我们的服务的类名。它是唯一必需的属性enabled 为 true表明系统可以示例化这个服务。默认值为 trueapplication 也有自己的 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或其他组件调用这个方法停止目标servicestopSelf()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方法返回我们刚刚定义的那个内部类实例重写ServiceConnectiononServiceConnected时调用逻辑方法 绑定服务方法二通过接口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:namecom.baidu.location.fandroid:enabledtrueandroid:process:remote/service6. 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_COMPATIBILITYSTART_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. 使用IntentServiceIntentService 是 Scrvice 的子类因此它不是普通的 Service它比普通的Service 增加了额外的功能。先看 Service 本身存在的两个问题。Service不会专门启动一个单独的进程Service与它所在应用位于同一个进程中。Service不是一条新的线程因此不应该在Service中直接处理耗时的任务。IntentService正好弥补了Service的不足。IntentService的特点IntentService会创建单独的worker线程来处理所有的Intent请求。IntentService会创建单独的worker线程来处理onHandleIntent()方法实现的代码因此开发者无须处理多线程问题。IntentService实例创建 SccIntentService.java 继承自 IntentService 类重写 onHandleIntent() 方法、创建一个无参构造函数其代码如下:publicclassSccIntentServiceextendsIntentService{publicSccIntentService(){super(SccIntentService);}OverrideprotectedvoidonHandleIntent(Intent intent){MLog.e(getClass().getName(),onHandleWork);for(int i0;i3;i){try{MLog.e(getClass().getName(),Number:开始i);Thread.sleep(10000);MLog.e(getClass().getName(),Number:结束i);}catch(InterruptedException e){e.printStackTrace();}}}OverridepublicvoidonDestroy(){super.onDestroy();MLog.e(getClass().getName(),onDestroy);}}\2. 添加 IntentService 组件声明在 AndroidManifest.xml 文件中声明一个 Service 组件其代码如下:service android:name.service.SccIntentService/\3. 启动 SccIntentServicestartService(newIntent(ServiceActivity.this,SccIntentService.class));/pre\4. 运行结果07-0718:00:39.505E/-SCC-:com.scc.demo.actvitiy.ServiceActivityonCreate07-0718:00:39.531E/-SCC-:com.scc.demo.actvitiy.ServiceActivityonStart07-0718:00:39.531E/-SCC-:com.scc.demo.actvitiy.ServiceActivityonResume07-0718:01:12.690E/-SCC-com.scc.demo.service.SccIntentService:onHandleWork07-0718:01:12.690E/-SCC-com.scc.demo.service.SccIntentService:Number:开始007-0718:01:22.691E/-SCC-com.scc.demo.service.SccIntentService:Number:结束007-0718:01:22.697E/-SCC-com.scc.demo.service.SccIntentService:Number:开始107-0718:01:32.698E/-SCC-com.scc.demo.service.SccIntentService:Number:结束107-0718:01:32.698E/-SCC-com.scc.demo.service.SccIntentService:Number:开始207-0718:01:42.699E/-SCC-com.scc.demo.service.SccIntentService:Number:结束207-0718:01:42.716E/-SCC-com.scc.demo.service.SccIntentService:onDestroy普通 Service 直接执行 20S 的的耗时操作会阻塞主线程造成 ANR (程序无响应)异常。IntentService 执行30S的耗时操作不会阻塞主线程更不会产生ANR。如上图开始18:01:1218:01:42长达30S正常运行未产生ANR。IntentService还有个好处就是 「用完即走」。执行完onHandleIntent()方法里面的耗时操作后自行调用onDestroy()方法进行关闭。更多Android零基础入门到精通的资料完整资料可以扫码免费领取## 【腾讯技术团队出品】Android零基础入门到精通Android Studio安装教程全套安卓基础教程Android编程入门教程Java语言基础从入门到熟悉Kotlin语言基础从入门到熟悉Android 技术栈从入门到熟悉Android Jetpack 全家桶全面学习对于新手来说可能安装Android Studio存在一定困难你可以看着以下视频一步步的跟着学习安装运行Android Studio 安装教程有了Java阶段的学习这一阶段建议以视频学习为主辅以图书查漏补缺。如果以图书为主可以根据图书讲解敲代码辅以教学视频查漏补缺。遇到问题可以去百度入门的问题一般会有很多人遇到并且给出比较好的解答。需要掌握基本知识点比如四大组件如何使用、如何创建Service、如何进行布局、简单的自定义View、动画、网络通信等常见技术。全套零基础教程已经为你们准备好了需要的可以添加下方二维码免费领取全套安卓基础教程