Android开发:如何正确绑定NotificationListenerService

Android开发:如何正确绑定NotificationListenerService

NotificationListenerService是Android提供的用于监听系统通知消息的服务类。在继承该类并重载相关方法后,即可在APP中读取和操作通知。(该部分过程不再赘述)

要使得NotificationListenerService获取的信息能够在APP中使用,需要在Fragment中实现与服务的通信。Android提供了三种方式:扩展 Binder 类使用 Messenger使用 AIDL。其中后两种方式都需要自行处理消息的传递,而扩展Binder类可以实现在Fragment或者Activity中直接调用服务的实例,在调用相关成员方法时较为方便。

为叙述方便清晰,假设自行编写的通知管理类为class MyNotiService:NotificationListenerService()

绑定服务常规操作

官方文档给出的扩展Binder类方法(在MyNotiService中定义):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class LocalService : Service() {
    // Binder given to clients
    private val binder = LocalBinder()
    inner class LocalBinder : Binder() {
        fun getService(): LocalService = this@LocalService
    }
    override fun onBind(intent: Intent): IBinder {
        return binder
    }
}

此时,在需要绑定该服务的上下文(以Activity为例)中,需要使用ServiceConnection来管理服务的绑定情况,在Activity创建时需要通过发送Intent请求绑定服务,在Intent发出后便会调用服务类的OnBind方法返回IBinder。

注意,如果请求绑定和解绑写在onCreate和OnDestroy对,则在Activity存活时始终绑定;如果写在onStart和onStop,则只有在Activity前台运行时绑定服务。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class BindingActivity : Activity() {
    private lateinit var mService: LocalService
    private var mBound: Boolean = false
    /** Defines callbacks for service binding, passed to bindService()  */
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            val binder = service as LocalService.LocalBinder
            mService = binder.getService()
            mBound = true
        }
        override fun onServiceDisconnected(arg0: ComponentName) {
            mBound = false
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        // Bind to LocalService
        Intent(this, LocalService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }
    override fun onDestroy() {
        super.onStop()
        unbindService(connection)
        mBound = false
    }
}

上述绑定过程完成后,在Activity便可以直接调用mService这一实例及其成员。

错误、现象与分析

然而,如果将上述的代码应用于MyNotiService,会发现是不可行的。代码运行后将会发现OnListenerConnected方法将永远不会被调用,也就无法正常获取系统的通知。

其原因在于,NotificationListenerService属于需要请求系统的权限的特殊服务,在用户打开“通知管理权”页面授予通知读取权限的时刻,Android系统进程将会首先绑定MyNotiService服务。这一与系统的绑定是NotificationListenerService父类中定义的默认onBind方法实现的。

这也就是说,MyNotiService中要么不override onBind,要么就得显式调用父类的onBind使得能够正常与系统进程绑定,否则就会无法正常获取通知。

override fun onBind(intent: Intent): IBinder {
	return super.onBind(intent)!!
}

经过上述分析,这时就处于一个尴尬的局面:如果与系统绑定,就无法返回自己的Binder;如果返回自己的Binder,就无法实现通知管理功能。

解决方法

最终在StackOverflow的这个问题下找到了解决方法。其原理是利用Intent可以携带额外信息的特点,通过一个简单的判断来确定发起绑定的是系统还是我们自己的Activity,然后返回正确的Binder。

例如,在我们自己的Activity中发送绑定Intent时,在extras中附带一个Boolean类型的值selfCall=true,表明绑定请求是从我们自己的程序发起的;

1
2
3
4
5
6
7
8

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Bind to LocalService
    Intent(requireContext(), NotiTrayService::class.java).putExtra("selfCall",true) .also { intent ->
	requireContext().bindService(intent, connection, Context.BIND_AUTO_CREATE)
	}
}

而在MyNotiService中,只需判断接收的参数Intent中是否有这个附加值,如果有,就返回自行扩展带Binder,否则就返回父类方法的返回值。

1
2
3
4
5
6
7
8
override fun onBind(intent: Intent): IBinder {
    val selfCall=intent.extras?.getBoolean("selfCall",false)
    return if(selfCall!=null && selfCall==true){
        mybinder
    }else{
        super.onBind(intent)!!
    }
}

这样就可以在自己的Activity中绑定并调用MyNotiService,并且不影响正常的获取通知的功能了。

其他数据传递方案

单例模式(不可行)

在单例模式(Singleton)中,任何时候类至多只有一个实例,并且该实例可以通过调用类方法获取。除了使用object代替`class``关键字外,还可以用下述方法实现单例模式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class SingletonDemo private constructor() {
    companion object {
        private var instance: SingletonDemo? = null
        get() {//instance getter
            if (field == null) {
                field = SingletonDemo()
            }
            return field
        }
        fun get(): SingletonDemo{
            //不能使用getInstance作为方法名,因为companion object内部已经占用
            return instance!!
        }
    }
}

在使用时,只需要调用SingletonDemo.get()即可获得唯一实例。

乍一看,Android中的服务类在运行时也只具有一个实例,似乎十分适合采用单例模式。然而如果将上述单例模式的代码应用于MyNotiService,却会发现无法正常运行。

其原因是Android的Service不是普通的kotlin类,在field=SingletonDemo()语句中创建的实例只是一个普通的kotlin对象,还不具有服务功能。

因此使用单例模式是无法实现在Activity中调用服务方法的。

静态成员变量/方法

对于一些常用的状态变量,也可以用静态成员变量通过MutableLiveData等类型传递。例如在服务中声明一个静态的Boolean成员,用于表示服务是否正在运行(LifecycleOwner是使用MutableLiveData所需的生命周期父类):

1
2
3
4
5
6
class MyNotiService: NotificationListenerService(),LifecycleOwner{
    companion object{
        @JvmField
        var isRunning=MutableLiveData(false)
	}
}

在Activity中,即可添加关于上述变量的监听事件:

1
2
3
4
5
6
//监听
MyNotiService.isRunning.observe(viewLifecycleOwner) { isRunning ->
       //do something     
}
//修改(例如手动停止服务)
MyNotiService.isRunning.value=false

这样就完成了简单的数据传递。

然而这种方法的缺点是,所有数据的更新都需要在MyNotiService内部手动处理,会增加许多冗余的更新代码。另外如果想要获取实例方法如activeNotifications的返回值,只能通过一个中间变量存储,再用一个成员方法主动更新。这样的更新往往会滞后于通知的更新,而且也不一定在所有需要更新的时刻都有合适的回调。

0%