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保留。

三、属性配置

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

四、开启关闭

4.1 start 方式

  • 长期后台运行(不会因为APP或者Activity销毁而停止,但服务进程在内存不足时会被回收),外部不能调用Service里的方法。
  • 每调用一次startService(),onStartCommand()就会执行一次,但实际上每个 Service 只会存在一个实例。所以不管调用了多少次 startService(),只需调用一次 stopService() 或 stopSelf() ,Service就会停止。stopSelf() 是 Service 内部自己调用的。
class MyService : Service() {
    //第一次创建的时候才调用
    override fun onCreate() { super.onCreate() }
    //每次启动的时候都会调用
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }
    //销毁的时候调用
    override fun onDestroy() { super.onDestroy() }
}

Activity {
    //开启和关闭不要用同一个intent,当退出Activity后开启的intent就是null,关闭调用报错。
    //实际开发开启和注销服务都写在Activity的onStart()、onDestroy()里面就没有这个问题
    btn1.setOnClickListener {
        val intent = Intent(this, MyService::class.java)
        startService(intent)    //启动服务
    }
    btn2.setOnClickListener {
        val intent = Intent(this, MyService::class.java)
        stopService(intent)    停止服务
    }
}

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之间的通信。
//抽取用于暴露Service中供外部调用的功能
interface IFunction {
    fun callEat()
    fun callJump()
}

class MyService : Service() {
    //绑定时调用
    override fun onBind(intent: Intent): IBinder {
        return MyBinder()   //返回代理人实例,供外部Client与Service通讯
    }
    //解绑时调用
    override fun onUnbind(intent: Intent?): Boolean {
        return super.onUnbind(intent)
    }
    //销毁的时候调用
    override fun onDestroy() {
        super.onDestroy()
    }
    //Service中自定义的函数
    fun eat() {}
    fun jump() {}
    //定义代理人,在代理人中提供调用Service的对应方法
    inner class MyBinder : Binder(), IFunction {
        override fun callEat() { eat() }
        override fun callJump() { jump() }
    }
}

Activity {
    private lateinit var function: IFunction
    private val connection = object : ServiceConnection {
        //绑定时调用
        override fun onServiceConnected(name: ComponentName, binder: IBinder) {
            //抽取成属性供别处调用
            function = binder as IFunction    //转为抽取的接口对象来调用Service暴露的功能
        }
        //解绑时调用
        override fun onServiceDisconnected(name: ComponentName) {...}
    }
    btn1.setOnClickListener{
        val intent = Intent(this, MyService::class.java)
        bindService(intent, connection, Context.BIND_AUTO_CREATE)    //绑定Service
    }
    btn2.setOnClickListener{
        val intent = Intent(this, MyService::class.java)
        unbindService(connection)    //解绑Service
    }
    btn3.setOnClickListener{
        function.callJump()    //就可以调用Service里的方法了
        function.callEat()
    }
}

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优先级较低,当系统出现内存不足情况时,很有可能会被回收。

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

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

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

override fun onDestroy() {
    super.onDestroy()
    stopForeground(1)    //开启时设置的唯一标识
}

七、耗时任务 IntentService

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

class MyService : Service() {
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        thread {
            //耗时任务
            stopSelf()    //停止Service
        }
    return super.onStartCommand(intent, flags, startId)
    }
}

class MyIntentService : IntentService("MyIntentService") {    //传入的字符串随意,只在调试的时候有用
    override fun onHandleIntent(intent: Intent?) {
        //耗时任务
    }
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyIntentService", "有自动停止")
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值