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调试的开关等)
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球 : 欢迎加入,多谢支持~
一个人可以走的更快 , 一群人可以走的更远