Android Tech And Perf

Android Service:开发自己的通知中心(2):辅助性服务实战

Word count: 1.8kReading time: 8 min
2014/04/01

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必须要重写几个重要的方法:

  1. onServiceConnected方法负责在服务和Activity绑定的时候,进行初始化数据,这里新建了一个AccessibilityServiceInfo对象,并将TYPE_NOTIFICATION_STATE_CHANGED、TYPE_WINDOW_STATE_CHANGED、TYPE_WINDOW_CONTENT_CHANGED纳入监听范围,TYPE_NOTIFICATION_STATE_CHANGED表示这个服务可以监听Notification的变化,我们正是使用这个特性来实现第三方的通知中心功能。
  2. onInterrupt是服务断开时调用的函数
  3. 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的实战就讲到这里,这一篇博文也是拖了一段时间才写完的,也算是为前一段时间的项目做个了结。

项目中目前还存在的问题:

  1. 无法获取安装这个应用之前的系统的Notification
  2. 得到的Notification对象没法保存在本地,所以这个服务被杀掉之后,所有的数据都会丢失。(试过用db4o这种对象数据库来进行存储,发现行不通)
  3. 对Android系统的Notification对象的行为模仿不够(有些系统的事件监听不到,比如usb的插拔、usb调试的开关等)

关于我 && 博客

  1. 关于我 , 非常希望和大家一起交流 , 共同进步 .
  2. 博客内容导航
  3. 优秀博客文章记录 - Android 性能优化必知必会

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. 1.辅助性服务实战介绍
  2. 2. 2.开发第三方通知中心
    1. 2.1. 2.1继承AccessibilitySerivce
    2. 2.2. 2.2在Manifest中注册service
    3. 2.3. 2.3 接受并处理Notification
  3. 3. 3.总结和问题
  • 关于我 && 博客