1.辅助性服务实战介绍
上一篇文章介绍了什么是Accessibility以及简单的使用,这一篇文章就来讲讲如何使用Accessibility服务来创建一个简单的Android通知中心。Android中通知中心是一个系统层面的服务,负责显示应用和系统发来的通知(Notification,比如USB插入、选择输入法、未接来电、截图、天气信息、新闻推送等等)。在android4.3之前,一般的第三方应用是无法获取Notification list的(在Android4.3之后,有了一个新的接口,NotificationListenerService.getActiveNotifications(),可以获取当前的Notification)。但是利用Accessibility服务可以监听到各种事件的特性,可以开发一个第三方的通知中心,实现与系统通知栏类似的功能。
下面就来介绍如何开发自己的通知中心。
2.开发第三方通知中心
2.1继承AccessibilitySerivce
按照上一篇辅助性服务的介绍,一个辅助性服务可以被捆绑到一个标准的应用程序上,或者以一个单独的安卓工程被创建,我们这里建立一个服务,继承AccessibilitySerivce
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
import
android
.
accessibilityservice
.
AccessibilityService
;
import
android
.
accessibilityservice
.
AccessibilityServiceInfo
;
import
android
.
app
.
Notification
;
import
android
.
app
.
PendingIntent
;
import
android
.
content
.
Intent
;
import
android
.
os
.
Parcelable
;
import
android
.
util
.
Log
;
import
android
.
view
.
accessibility
.
AccessibilityEvent
;
import
android
.
view
.
accessibility
.
AccessibilityNodeInfo
;
import
android
.
view
.
accessibility
.
AccessibilityRecord
;
import
android
.
widget
.
Toast
;
public
class
NotificationFetcherService
extends
AccessibilityService
{
private
static
final
String
TAG
=
"NotificationFetcherService: "
;
public
void
onAccessibilityEvent
(
AccessibilityEvent
event
)
{
if
(
!
(
event
.
getEventType
(
)
==
AccessibilityEvent
.
TYPE_NOTIFICATION_STATE_CHANGED
)
)
{
return
;
}
Notification
localNotification
=
(
Notification
)
event
.
getParcelableData
(
)
;
if
(
localNotification
!=
null
)
{
Intent
intent
=
new
Intent
(
)
;
intent
.
putExtra
(
"NotifyData"
,
localNotification
)
;
intent
.
setAction
(
".NotificationFetcherService"
)
;
sendBroadcast
(
intent
)
;
}
}
@Override
protected
void
onServiceConnected
(
)
{
// Define it in both xml file and here, for compatibility with pre-ICS devices
AccessibilityServiceInfo
info
=
new
AccessibilityServiceInfo
(
)
;
info
.
eventTypes
=
AccessibilityEvent
.
TYPE_NOTIFICATION_STATE_CHANGED
|
AccessibilityEvent
.
TYPE_WINDOW_STATE_CHANGED
|
AccessibilityEvent
.
TYPE_WINDOW_CONTENT_CHANGED
;
info
.
feedbackType
=
AccessibilityServiceInfo
.
FEEDBACK_GENERIC
;
setServiceInfo
(
info
)
;
}
@Override
public
void
onInterrupt
(
)
{
System
.
out
.
println
(
"onInterrupt"
)
;
}
}
|
继承AccessibilitySerivce必须要重写几个重要的方法:
onServiceConnected方法负责在服务和Activity绑定的时候,进行初始化数据,这里新建了一个AccessibilityServiceInfo对象,并将TYPE_NOTIFICATION_STATE_CHANGED、TYPE_WINDOW_STATE_CHANGED、TYPE_WINDOW_CONTENT_CHANGED纳入监听范围,TYPE_NOTIFICATION_STATE_CHANGED表示这个服务可以监听Notification的变化,我们正是使用这个特性来实现第三方的通知中心功能。
onInterrupt是服务断开时调用的函数
onAccessibilityEvent是最重要的,它负责监听所注册的eventTypes(在onServiceConnected中注册的)的事件。从上面的代码中我们可以得到一个Notification对象:
1
|
Notification
localNotification
=
(
Notification
)
event
.
getParcelableData
(
)
;
|
得到Notification对象之后,就可以进行自己的操作,我这里是通过广播的形式,将收到的Notification发送给Activity进行处理。
这里也会碰到一个小问题:当一个Notification对象太大时(比如截图、未接来电等,Notification.contentView就很大,通过广播传播会出现data过大无法传输的问题),这时可以把Notification.contentView对象暂时保存在Application中,然后再置为null,Activity中接收到数据后,再进行赋值。
2.2在Manifest中注册service
1
2
3
4
5
6
7
8
9
10
11
|
<
service
android
:
name
=
".NotificationFetcherService"
android
:
permission
=
"android.permission.BIND_ACCESSIBILITY_SERVICE"
>
<
intent
-
filter
>
<
action
android
:
name
=
"android.accessibilityservice.AccessibilityService"
/
>
<
/
intent
-
filter
>
<
meta
-
data
android
:
name
=
"android.accessibilityservice"
android
:
resource
="
@xml
/
accessibilityserviceconfig
/
>
<
/
service
>
|
这里就是普通的service注册,注意 <mate-data>标签中的xml文件:从Android 4.0版本开始,有另外一种方法:使用XML文件来配置这类服务。如果你以XML的形式来定义你的服务,某些像canRetrieveWindowContent可配置的选项就可用了。和service一样的配置,使用XML来定义。如果你要使用XML路径,要在你的mainfest文件中指定它,在你的服务声明中添加<meta-data>标签,并指向这个XML资源文件。比如上面的代码,我们在res/xml/中建立accessibilityaseviceconfig.xml,内容如下:
1
2
3
4
5
|
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
accessibility
-
service
xmlns
:
android
=
"http://schemas.android.com/apk/res/android"
android
:
accessibilityEventTypes
=
"typeWindowStateChanged|typeNotificationStateChanged|typeWindowContentChanged"
android
:
accessibilityFeedbackType
=
"feedbackGeneric"
/
>
|
服务这里就配置好了。
2.3 接受并处理Notification
下面的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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
import
android
.
app
.
Activity
;
import
android
.
app
.
Application
;
import
android
.
app
.
Notification
;
import
android
.
app
.
PendingIntent
;
import
android
.
app
.
PendingIntent
.
CanceledException
;
import
android
.
content
.
BroadcastReceiver
;
import
android
.
content
.
Context
;
import
android
.
content
.
Intent
;
import
android
.
content
.
IntentFilter
;
import
android
.
os
.
Bundle
;
import
android
.
os
.
Handler
;
import
android
.
os
.
Message
;
import
android
.
os
.
Parcelable
;
import
android
.
os
.
Process
;
import
android
.
text
.
method
.
ScrollingMovementMethod
;
import
android
.
util
.
Log
;
import
android
.
view
.
LayoutInflater
;
import
android
.
view
.
View
;
import
android
.
view
.
ViewGroup
;
import
android
.
widget
.
Button
;
import
android
.
widget
.
LinearLayout
;
import
android
.
widget
.
RemoteViews
;
import
android
.
widget
.
TextView
;
public
class
NotificaitonActivity
extends
Activity
{
private
static
final
int
NOTIFY_DATA_FLAG
=
1
;
private
static
final
String
NOTIFY_DATA_ID_STR
=
"NotifyData"
;
private
NotifyDataReceiver
receiver
;
private
TextView
textView
;
private
LinearLayout
rootLayout
;
private
Button
button
;
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
)
;
setContentView
(
R
.
layout
.
activity_main
)
;
textView
=
(
TextView
)
findViewById
(
R
.
id
.
notify_test_textview
)
;
textView
.
setMovementMethod
(
ScrollingMovementMethod
.
getInstance
(
)
)
;
rootLayout
=
(
LinearLayout
)
findViewById
(
R
.
id
.
root_layout
)
;
registerBroadcast
(
)
;
button
=
(
Button
)
findViewById
(
R
.
id
.
test_button
)
;
button
.
setOnClickListener
(
new
View
.
OnClickListener
(
)
{
@Override
public
void
onClick
(
View
v
)
{
Button
b
=
new
Button
(
NotificaitonActivity
.
this
)
;
b
.
setText
(
"Tthis"
)
;
rootLayout
.
addView
(
b
)
;
}
}
)
;
}
private
void
registerBroadcast
(
)
{
receiver
=
new
NotifyDataReceiver
(
)
;
IntentFilter
filter
=
new
IntentFilter
(
)
;
filter
.
addAction
(
".NotificationFetcherService"
)
;
this
.
registerReceiver
(
receiver
,
filter
)
;
Log
.
e
(
"Dx:"
,
"Broadcast registered........."
)
;
}
private
void
addToUi
(
RemoteViews
remoteView
)
{
rootLayout
.
addView
(
remoteView
)
;
}
private
void
showNotify
(
String
notiString
)
{
textView
.
setText
(
textView
.
getText
(
)
+
"\n"
+
notiString
)
;
}
private
class
NotifyDataReceiver
extends
BroadcastReceiver
{
@Override
public
void
onReceive
(
Context
context
,
Intent
intent
)
{
Log
.
e
(
"Dx:"
,
"Receiver got msg in onReceive()..."
)
;
Parcelable
notifyParcelable
=
intent
.
getParcelableExtra
(
"NotifyData"
)
;
if
(
notifyParcelable
!=
null
)
{
Notification
notification
=
(
Notification
)
notifyParcelable
;
showNotify
(
"tickerText: "
+
notification
.
tickerText
)
;
showNotify
(
"toString: "
+
(
String
)
(
notification
.
toString
(
)
)
)
;
RemoteViews
remoteV
=
notification
.
contentView
;
if
(
remoteV
==
null
)
{
showNotify
(
"remoteView is: null"
)
;
}
else
{
showNotify
(
"remoteView is: not null"
)
;
addToUi
(
remoteV
)
;
}
PendingIntent
pendIntent
=
notification
.
contentIntent
;
if
(
pendIntent
==
null
)
{
showNotify
(
"pendIntent is: null"
)
;
}
else
{
showNotify
(
"pendIntent is: not null"
)
;
}
showNotify
(
"**************************"
)
;
showNotify
(
" "
)
;
}
}
}
}
|
注:这里有很重要的一点,由于AccessibilityService的特殊性,用户必须手动到设置-辅助功能中,打开对应的服务,我们才可以通过AccessibilityService获得对应的数据,这一点非常重要。
上面的Activity只是简单地显示Notification,关于更多Notification的操作,可以参考Notification这个类,其中重要的属性有:contentView,flags。要模拟真正的通知中心,还是要费一番功夫的。这里由于公司项目的保密,暂不提供对应的实现代码(其实得到Notification就已经成功了一半了),有兴趣的同学可以私下和我交流。
3.总结和问题
AccessibilityService的实战就讲到这里,这一篇博文也是拖了一段时间才写完的,也算是为前一段时间的项目做个了结。
项目中目前还存在的问题:
- 无法获取安装这个应用之前的系统的Notification
- 得到的Notification对象没法保存在本地,所以这个服务被杀掉之后,所有的数据都会丢失。(试过用db4o这种对象数据库来进行存储,发现行不通)
- 对Android系统的Notification对象的行为模仿不够(有些系统的事件监听不到,比如usb的插拔、usb调试的开关等)
上面的问题,如果你有好的想法,我们私下交流。
本文固定地址:http://www.grackertalk.com/?p=66 转载请注明出处。