• Android - Service


    前台20s后台200s不执行玩就报ANR异常。

    一、概念

    没有界面在后台长期运行在主线程中的一个组件。

    1.1 与线程的区别 

    ServiceThread
    可以配置执行在不同的进程中。CPU调度的最小单位。
    任何有Context的地方都可以控制Service当Activity销毁后不再持有该Thread的引用,不管该子线程是一次任务还是循环任务都无法再控制。

    1.2 两种方式开启服务的区别

    start方式bind方式
    使用场景启动一个后台服务长期执行某个任务。

    生命周期和Activity绑定。外部需要与服务通讯,调用服务中的方法。公开接口供客户端远程调用,绑定时才会执行。

    生命周期onCreate→onStartCommand→onDestroy

    onCreate→onBind→onUnbind→onDestroy

    多次开启每次都执行onStartCommand无效果
    多次关闭抛异常?抛异常

    二、生命周期

     所有和界面相关生命周期都没有。如果Service或者Activity是new出来的就是普通类不是组件了,这样调用里面的方法没意义。

    2.1 onStartCommand()中的返回值

    系统会因为内存不足而销毁Service,是可以等到内存充足后再重建Service,并执行onStartCommand()。

    返回值类型说明

    return super.onStartCommand(intent, flags, startId)

    return Service.START_NOT_STICKY

    默认情况,被销毁后不会重建。
    return Service.START_STICKY被销毁后会重建。但是不再保存onStartCommand()中的形参intent。
    return Service.START_REDELIVER_INTENT被销毁后会重建。会将销毁钱最后一次传入onStartCommand()中的Intent保留。

    三、属性配置

    1. <service
    2. android:name=".MyService" //Service的类名
    3. android:label //Service的名字若不设置默认为Service类名
    4. android:icon //Service的图标
    5. android:permission //申明此Service的权限有提供了该权限的应用才能控制或连接此服务
    6. android:process //表示该服务是否在另一个进程中运行远程服务),不设置默认为本地服务remote则设置成远程服务
    7. android:foregroundServiceType="location|camera|microphone" //在前台Service中获取定位/摄像头/麦克风权限需要配置Android10引入定位11引入摄像头麦克风
    8. android:enabled="true" //是否默认启动默认为 true
    9. android:exported="true" //该服务是否能够被其他应用程序所控制或连接 不设置默认此项为 true
    10. >
    11. <intent-filter android:priority="1000" /> //配置优先级,最大1000
    12. service>

    四、开启关闭

    4.1 start 方式

    • 长期后台运行(不会因为APP或者Activity销毁而停止,但服务进程在内存不足时会被回收),外部不能调用Service里的方法。
    • 每调用一次startService(),onStartCommand()就会执行一次,但实际上每个 Service 只会存在一个实例。所以不管调用了多少次 startService(),只需调用一次 stopService() 或 stopSelf() ,Service就会停止。stopSelf() 是 Service 内部自己调用的。
    1. class MyService : Service() {
    2. //第一次创建的时候才调用
    3. override fun onCreate() { super.onCreate() }
    4. //每次启动的时候都会调用
    5. override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
    6. return super.onStartCommand(intent, flags, startId)
    7. }
    8. //销毁的时候调用
    9. override fun onDestroy() { super.onDestroy() }
    10. }
    11. Activity {
    12. //开启和关闭不要用同一个intent,当退出Activity后开启的intent就是null,关闭调用报错。
    13. //实际开发开启和注销服务都写在Activity的onStart()、onDestroy()里面就没有这个问题
    14. btn1.setOnClickListener {
    15. val intent = Intent(this, MyService::class.java)
    16. startService(intent) //启动服务
    17. }
    18. btn2.setOnClickListener {
    19. val intent = Intent(this, MyService::class.java)
    20. stopService(intent) 停止服务
    21. }
    22. }

    4.2 bind 方式

    • 生命周期和Activity同生共死,外部可以调用Service中的方法,可以和多个Activity绑定共享。
    • 隐形的服务系统设置里面查不到,Activity死的时候Service也死了,不能长期在后台运行。bindService()绑定服务会调用onCreate() => onBind(),unbundService()解绑服务会调用onUnbind() => onDestroy()。绑定后再绑定无效果,解绑后再解绑抛异常,因此写到Activity的onCreate()和onDestroy()中。
    • Binder是可以用作Service和Client之间通信,无论Service和Client是否在同一个进程内,Binder都可以完成Service和Client之间的通信。
    1. //抽取用于暴露Service中供外部调用的功能
    2. interface IFunction {
    3. fun callEat()
    4. fun callJump()
    5. }
    6. class MyService : Service() {
    7. //绑定时调用
    8. override fun onBind(intent: Intent): IBinder {
    9. return MyBinder() //返回代理人实例,供外部Client与Service通讯
    10. }
    11. //解绑时调用
    12. override fun onUnbind(intent: Intent?): Boolean {
    13. return super.onUnbind(intent)
    14. }
    15. //销毁的时候调用
    16. override fun onDestroy() {
    17. super.onDestroy()
    18. }
    19. //Service中自定义的函数
    20. fun eat() {}
    21. fun jump() {}
    22. //定义代理人,在代理人中提供调用Service的对应方法
    23. inner class MyBinder : Binder(), IFunction {
    24. override fun callEat() { eat() }
    25. override fun callJump() { jump() }
    26. }
    27. }
    28. Activity {
    29. private lateinit var function: IFunction
    30. private val connection = object : ServiceConnection {
    31. //绑定时调用
    32. override fun onServiceConnected(name: ComponentName, binder: IBinder) {
    33. //抽取成属性供别处调用
    34. function = binder as IFunction //转为抽取的接口对象来调用Service暴露的功能
    35. }
    36. //解绑时调用
    37. override fun onServiceDisconnected(name: ComponentName) {...}
    38. }
    39. btn1.setOnClickListener{
    40. val intent = Intent(this, MyService::class.java)
    41. bindService(intent, connection, Context.BIND_AUTO_CREATE) //绑定Service
    42. }
    43. btn2.setOnClickListener{
    44. val intent = Intent(this, MyService::class.java)
    45. unbindService(connection) //解绑Service
    46. }
    47. btn3.setOnClickListener{
    48. function.callJump() //就可以调用Service里的方法了
    49. function.callEat()
    50. }
    51. }

    4.3 混合方式

    • 既长期在后台运行,又能调用 Service 里面的方法。
    • 在 Activity 的 onCreate() 中 startService() 并 bindService(),顺序无所谓。
    • 在 Activity 的 onDestroy()  中 unbindService(),根据情况在需要的地方stopService(),顺序无所谓两者都调用才会销毁服务。
    • startService() 后,不管是否有 Activity 进行 bindService() 或 unbindService(),Service都在后台运行着,直到调用 stopService() 或 stopSelf() 才会关闭,或者系统资源不足时被杀死。

    五、进程间通信

    详见Android 进程

    Aidl接口描述语言,专门用来解决调用远程服务的方式。

    调用第三方支付中APP中的付款功能,要将支付信息传递过去:

    ①用隐式意图开启到对方Service
        Intent intent = new Intent();
        intent.setAction("cn.hugmua.demo.remote");
        bindService(intent,conn,BIND_AUTO_CREATE);    
    ②远程应用中,把暴露出来的接口Iservice.java改成Iservice.aidl,删除public权限修饰(public是包和包之间,而现在用于多个工程之间)
    ③远程应用中,自定义的MyBinder只继承Stub。在gen目录里生成了新的Iservice.java,里面的Stub帮我们继承了Binder和实现了Iservice
    ④本地应用中,创建和远程服务中相同包名,并把Iservice.aidl复制过来,gen目录下会自动生成相应报名文件夹,里面有Iservice.java
    ⑤本地应用中,在连接器ServiceConnection的onServiceConnected()中,调用Stub静态方法anInterface()将IBinder转换为Iservice
        public void onServiceConnected(ComponentName name, IBinder service) {
            iservice = Stub.anInterface(service);
        }
    ⑥现在就可以用iservice对象调用远程服务里的方法了,要处理异常。

    六、前台Service(保活)

    从Android 8.0开始,只有APP保持在前台可见状态的情况下 Service 才能保证稳定运行,一旦进入后台 Service 随时都有可能被系统回收,防止恶意APP长期在后台占用手机资源。因此需要 Service 能一直保持运行状态就可以使用前台Service。

    • 前台Service在状态栏里会显示图标,下拉通知栏有显示通知。(这样APP就以另一种形式保持前台可见让用户清楚得知道什么APP占用着资源)
    • 前台Service优先级较高,不会由于系统内存不足而被回收;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收。

    从Android 9.0系统开始,必须在 Manifest 中进行权限声明。

    1. override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    2. //构建"点击通知后打开MainActivity"的Intent对象
    3. val myIntent = Intent(this, MainActivity::class.java)
    4. val pendingIntent = PendingIntent.getActivity(this, 0, myIntent, 0)
    5. //构建通知
    6. val notification = Notification.Builder(this, "") //获取构建器
    7. .setContentTitle("标题") //设置通知的标题
    8. .setContentText("内容") //设置通知的内容
    9. .setSmallIcon(R.mipmap.ic_launcher) //设置状态栏小图标
    10. .setLargeIcon(R.mipmap.ic_launcher) //设置通知栏大图标
    11. .setContentIntent(pendingIntent) //设置点击通知后的操作
    12. .build() //构建一个通知
    13. //让Service变成前台Service,并在系统的状态栏显示出来
    14. startForeground(1, notification) //参数一唯一标识停止的时候还要用,参数二通知
    15. return super.onStartCommand(intent, flags, startId)
    16. }
    17. override fun onDestroy() {
    18. super.onDestroy()
    19. stopForeground(1) //开启时设置的唯一标识
    20. }

    七、耗时任务 IntentService

    Service默认运行在主线程,耗时操作需要开启子线程。Service一旦启动就会一直运行,直到调用 stopService()、stopSelf() 或被系统回收,我们可能会忘记调用。为了简单创建一个异步会自动停止的Service,可以使用IntentService。

    1. class MyService : Service() {
    2. override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
    3. thread {
    4. //耗时任务
    5. stopSelf() //停止Service
    6. }
    7. return super.onStartCommand(intent, flags, startId)
    8. }
    9. }
    10. class MyIntentService : IntentService("MyIntentService") { //传入的字符串随意,只在调试的时候有用
    11. override fun onHandleIntent(intent: Intent?) {
    12. //耗时任务
    13. }
    14. override fun onDestroy() {
    15. super.onDestroy()
    16. Log.d("MyIntentService", "有自动停止")
    17. }
    18. }
  • 相关阅读:
    开发仿抖音APP遇到的问题和解决方案
    go goroutine
    2017年某高校848数据结构真题复习
    【蓝桥杯省赛真题37】Scratch三国演义字数统计 少儿编程scratch编程蓝桥杯省赛真题讲解
    Java Collections singletonList()方法具有什么功能呢?
    Qt中的数据库使用
    Nacos、ZooKeeper和Dubbo的区别
    nginx 部署前端资源的最佳方案
    【简单dp】舔狗舔到最后一无所有
    【趣学算法】分治算法
  • 原文地址:https://blog.csdn.net/HugMua/article/details/127711109